Merge "Move FrameMetricsObserver logic into UI Module."
diff --git a/Android.bp b/Android.bp
index e3aed6b..77abad6e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -200,34 +200,9 @@
 }
 
 filegroup {
-    name: "framework-wifi-sources",
-    srcs: [
-        "wifi/java/**/*.java",
-        "wifi/java/**/*.aidl",
-    ],
-    exclude_srcs: [
-        ":framework-wifi-non-updatable-sources"
-    ],
-    path: "wifi/java",
-}
-
-filegroup {
-    name: "framework-wifi-non-updatable-sources",
-    srcs: [
-        // TODO(b/146011398) package android.net.wifi is now split amongst 2 jars: framework.jar and
-        // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
-        // to a separate package.
-        "wifi/java/android/net/wifi/WifiNetworkScoreCache.java",
-        "wifi/java/android/net/wifi/WifiCondManager.java",
-        "wifi/java/android/net/wifi/wificond/*.java",
-    ],
-}
-
-filegroup {
     name: "framework-non-updatable-sources",
     srcs: [
         // Java/AIDL sources under frameworks/base
-        ":framework-appsearch-sources",
         ":framework-blobstore-sources",
         ":framework-core-sources",
         ":framework-drm-sources",
@@ -247,9 +222,8 @@
         ":framework-telecomm-sources",
         ":framework-telephony-common-sources",
         ":framework-telephony-sources",
-        ":framework-wifi-sources",
+        ":framework-wifi-annotations",
         ":framework-wifi-non-updatable-sources",
-	":libwificond_ipc_aidl",
         ":PacProcessor-aidl-sources",
         ":ProxyHandler-aidl-sources",
 
@@ -287,8 +261,11 @@
 filegroup {
     name: "framework-updatable-sources",
     srcs: [
+        ":framework-appsearch-sources",
         ":framework-sdkext-sources",
+        ":framework-statsd-sources",
         ":updatable-media-srcs",
+        ":framework-wifi-updatable-sources",
     ]
 }
 
@@ -424,7 +401,7 @@
 
 filegroup {
     name: "framework-jarjar-rules",
-    srcs: ["jarjar_rules_hidl.txt"],
+    srcs: ["framework-jarjar-rules.txt"],
 }
 
 filegroup {
@@ -449,6 +426,12 @@
     name: "framework-minus-apex",
     defaults: ["framework-defaults"],
     srcs: [":framework-non-updatable-sources"],
+    libs: [
+        "framework-appsearch-stubs",
+        // TODO(b/146167933): Use framework-statsd-stubs
+        "framework-statsd",
+        "framework-wifi-stubs",
+    ],
     installable: true,
     javac_shard_size: 150,
     required: [
@@ -484,7 +467,13 @@
     installable: false, // this lib is a build-only library
     static_libs: [
         "framework-minus-apex",
-        // TODO(jiyong): add stubs for APEXes here
+        "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
+        "framework-sdkext-stubs-systemapi",
+        // TODO(b/146167933): Use framework-statsd-stubs instead.
+        "framework-statsd",
+        // TODO(b/140299412): should be framework-wifi-stubs
+        "framework-wifi",
+        // TODO(jiyong): add more stubs for APEXes here
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
@@ -810,11 +799,7 @@
 filegroup {
     name: "incremental_aidl",
     srcs: [
-        "core/java/android/os/incremental/IIncrementalManagerNative.aidl",
-        "core/java/android/os/incremental/IIncrementalManager.aidl",
-        "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
         "core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
-        "core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",
     ],
     path: "core/java",
 }
@@ -823,6 +808,17 @@
     name: "dataloader_aidl",
     srcs: [
         "core/java/android/content/pm/IDataLoaderStatusListener.aidl",
+        "core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
+        "core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",
+    ],
+    path: "core/java",
+}
+
+filegroup {
+    name: "incremental_manager_aidl",
+    srcs: [
+        "core/java/android/os/incremental/IIncrementalManager.aidl",
+        "core/java/android/os/incremental/IIncrementalManagerNative.aidl",
     ],
     path: "core/java",
 }
@@ -832,9 +828,6 @@
     srcs: [
         ":incremental_aidl",
     ],
-    imports: [
-        "libdataloader_aidl",
-    ],
     backend: {
         java: {
             sdk_version: "28",
@@ -853,6 +846,9 @@
     srcs: [
         ":dataloader_aidl",
     ],
+    imports: [
+        "libincremental_aidl",
+    ],
     backend: {
         java: {
             sdk_version: "28",
@@ -861,8 +857,30 @@
             enabled: true,
         },
         ndk: {
+            enabled: false,
+        },
+    },
+}
+
+aidl_interface {
+    name: "libincremental_manager_aidl",
+    srcs: [
+        ":incremental_manager_aidl",
+    ],
+    imports: [
+        "libincremental_aidl",
+        "libdataloader_aidl",
+    ],
+    backend: {
+        java: {
+            sdk_version: "28",
+        },
+        cpp: {
             enabled: true,
         },
+        ndk: {
+            enabled: false,
+        },
     },
 }
 
@@ -1671,6 +1689,8 @@
         "core/java/android/util/LocalLog.java",
         "core/java/android/util/TimeUtils.java",
         "core/java/com/android/internal/os/SomeArgs.java",
+        "core/java/com/android/internal/util/AsyncChannel.java",
+        "core/java/com/android/internal/util/BitwiseInputStream.java",
         "core/java/com/android/internal/util/FastXmlSerializer.java",
         "core/java/com/android/internal/util/HexDump.java",
         "core/java/com/android/internal/util/IState.java",
diff --git a/apex/Android.bp b/apex/Android.bp
index 9ea3953..56f7db2 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -26,6 +26,9 @@
     "--hide Typo " +
     "--hide UnavailableSymbol "
 
+// TODO: remove this server classes are cleaned up.
+mainline_stubs_args += "--hide-package com.android.server "
+
 stubs_defaults {
     name: "framework-module-stubs-defaults-publicapi",
     args: mainline_stubs_args,
@@ -34,6 +37,6 @@
 
 stubs_defaults {
     name: "framework-module-stubs-defaults-systemapi",
-    args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi ",
+    args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ",
     installable: false,
 }
diff --git a/apex/appsearch/Android.bp b/apex/appsearch/Android.bp
index bcdcc7d..b014fdc 100644
--- a/apex/appsearch/Android.bp
+++ b/apex/appsearch/Android.bp
@@ -14,9 +14,11 @@
 
 apex {
     name: "com.android.appsearch",
-
     manifest: "apex_manifest.json",
-
+    java_libs: [
+        "framework-appsearch",
+        "service-appsearch",
+    ],
     key: "com.android.appsearch.key",
     certificate: ":com.android.appsearch.certificate",
 }
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 0a65f73..3dc5a2c 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -23,17 +23,52 @@
 
 java_library {
   name: "framework-appsearch",
-  installable: false,
-  sdk_version: "core_platform",
-  srcs: [
-    ":framework-appsearch-sources",
-  ],
-  aidl: {
-    export_include_dirs: [
-      "java",
-    ],
-  },
+  installable: true,
+  sdk_version: "core_platform", // TODO(b/146218515) should be core_current
+  srcs: [":framework-appsearch-sources"],
   libs: [
-    "framework-minus-apex",
+    "framework-minus-apex",  // TODO(b/146218515) should be framework-system-stubs
   ],
 }
+
+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: [
+        ":framework-annotations",
+        ":framework-appsearch-sources",
+    ],
+    aidl: {
+        include_dirs: ["frameworks/base/core/java"],
+    },
+    args: metalava_appsearch_docs_args,
+    sdk_version: "core_current",
+    libs: ["android_system_stubs_current"],
+}
+
+java_library {
+    name: "framework-appsearch-stubs",
+    srcs: [":framework-appsearch-stubs-srcs"],
+    aidl: {
+        export_include_dirs: [
+            "java",
+        ],
+    },
+    sdk_version: "core_current",
+    libs: ["android_system_stubs_current"],
+    installable: false,
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
index fcebe3d..02cc967 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManagerFrameworkInitializer.java
@@ -15,19 +15,25 @@
  */
 package android.app.appsearch;
 
+import android.annotation.SystemApi;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
 
 /**
- * This is where the AppSearchManagerService wrapper is registered.
+ * Class holding initialization code for the AppSearch module.
  *
- * TODO(b/142567528): add comments when implement this class
  * @hide
  */
+@SystemApi
 public class AppSearchManagerFrameworkInitializer {
+    private AppSearchManagerFrameworkInitializer() {}
 
     /**
-     * TODO(b/142567528): add comments when implement this class
+     * Called by {@link SystemServiceRegistry}'s static initializer and registers all AppSearch
+     * services to {@link Context}, so that {@link Context#getSystemService} can return them.
+     *
+     * @throws IllegalStateException if this is called from anywhere besides
+     *     {@link SystemServiceRegistry}
      */
     public static void initialize() {
         SystemServiceRegistry.registerStaticService(
diff --git a/apex/sdkext/Android.bp b/apex/sdkext/Android.bp
index aaf25b1..5369a96 100644
--- a/apex/sdkext/Android.bp
+++ b/apex/sdkext/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_visibility: [":__subpackages__"],
+}
+
 apex {
     name: "com.android.sdkext",
     manifest: "manifest.json",
@@ -19,12 +23,18 @@
     java_libs: [ "framework-sdkext" ],
     prebuilts: [
       "com.android.sdkext.ldconfig",
+      "cur_sdkinfo",
       "derive_sdk.rc",
     ],
     key: "com.android.sdkext.key",
     certificate: ":com.android.sdkext.certificate",
 }
 
+sdk {
+    name: "sdkext-sdk",
+    java_libs: [ "framework-sdkext-stubs-systemapi" ],
+}
+
 apex_key {
     name: "com.android.sdkext.key",
     public_key: "com.android.sdkext.avbpubkey",
@@ -42,3 +52,33 @@
     filename: "ld.config.txt",
     installable: false,
 }
+
+python_binary_host {
+    name: "gen_sdkinfo",
+    srcs: [
+        "sdk.proto",
+        "gen_sdkinfo.py",
+    ],
+    proto: {
+        canonical_path_from_root: false,
+    },
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+}
+
+gensrcs {
+    name: "cur_sdkinfo_src",
+    srcs: [""],
+    tools: [ "gen_sdkinfo" ],
+    cmd: "$(location) -v 0 -o $(out)",
+}
+
+prebuilt_etc {
+    name: "cur_sdkinfo",
+    src: ":cur_sdkinfo_src",
+    filename: "sdkinfo.binarypb",
+    installable: false,
+}
diff --git a/apex/sdkext/framework/Android.bp b/apex/sdkext/framework/Android.bp
index b17f0f8..a50dc3d 100644
--- a/apex/sdkext/framework/Android.bp
+++ b/apex/sdkext/framework/Android.bp
@@ -12,12 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_visibility: [ ":__pkg__" ]
+}
+
 filegroup {
     name: "framework-sdkext-sources",
     srcs: [
         "java/**/*.java",
     ],
     path: "java",
+    visibility: [ "//frameworks/base:__pkg__" ] // For the "global" stubs.
 }
 
 java_library {
@@ -27,4 +32,40 @@
     libs: [ "framework-annotations-lib" ],
     permitted_packages: [ "android.os.ext" ],
     installable: true,
+    visibility: [ "//frameworks/base/apex/sdkext:__pkg__" ],
+}
+
+droidstubs {
+    name: "framework-sdkext-droidstubs-publicapi",
+    defaults: [
+        "framework-sdkext-stubs-defaults",
+        "framework-module-stubs-defaults-publicapi",
+    ]
+}
+
+droidstubs {
+    name: "framework-sdkext-droidstubs-systemapi",
+    defaults: [
+        "framework-sdkext-stubs-defaults",
+        "framework-module-stubs-defaults-systemapi",
+    ]
+}
+
+stubs_defaults {
+    name: "framework-sdkext-stubs-defaults",
+    srcs: [
+        ":framework-sdkext-sources",
+        ":framework-annotations",
+    ],
+    sdk_version: "system_current",
+}
+
+java_library {
+    name: "framework-sdkext-stubs-systemapi",
+    srcs: [":framework-sdkext-droidstubs-systemapi"],
+    sdk_version: "system_current",
+    visibility: [
+      "//frameworks/base:__pkg__", // Framework
+      "//frameworks/base/apex/sdkext:__pkg__", // sdkext SDK
+    ]
 }
diff --git a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
index d3b9397..a8a7eff 100644
--- a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
+++ b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
@@ -60,7 +60,7 @@
      */
     public static int getExtensionVersion(@SdkVersion int sdk) {
         if (sdk < VERSION_CODES.R) {
-            throw new IllegalArgumentException();
+            throw new IllegalArgumentException(String.valueOf(sdk) + " does not have extensions");
         }
         return R_EXTENSION_INT;
     }
diff --git a/apex/sdkext/gen_sdkinfo.py b/apex/sdkext/gen_sdkinfo.py
new file mode 100644
index 0000000..5af478b
--- /dev/null
+++ b/apex/sdkext/gen_sdkinfo.py
@@ -0,0 +1,19 @@
+import sdk_pb2
+import sys
+
+if __name__ == '__main__':
+  argv = sys.argv[1:]
+  if not len(argv) == 4 or sorted([argv[0], argv[2]]) != ['-o', '-v']:
+    print('usage: gen_sdkinfo -v <version> -o <output-file>')
+    sys.exit(1)
+
+  for i in range(len(argv)):
+    if sys.argv[i] == '-o':
+      filename = sys.argv[i+1]
+    if sys.argv[i] == '-v':
+      version = int(sys.argv[i+1])
+
+  proto = sdk_pb2.SdkVersion()
+  proto.version = version
+  with open(filename, 'wb') as f:
+    f.write(proto.SerializeToString())
diff --git a/apex/sdkext/sdk.proto b/apex/sdkext/sdk.proto
new file mode 100644
index 0000000..d15b935
--- /dev/null
+++ b/apex/sdkext/sdk.proto
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+package com.android.sdkext.proto;
+
+option java_outer_classname = "SdkProto";
+option optimize_for = LITE_RUNTIME;
+
+message SdkVersion {
+  int32 version = 1;
+}
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index 5c46e1f..09ca1d2 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -22,7 +22,10 @@
     // libc.so and libcutils.so are included in the apex
     // native_shared_libs: ["libc", "libcutils"],
     // binaries: ["vold"],
-    java_libs: ["service-statsd"],
+    java_libs: [
+        "framework-statsd",
+        "service-statsd",
+    ],
     // prebuilts: ["my_prebuilt"],
     name: "com.android.os.statsd-defaults",
     key: "com.android.os.statsd.key",
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
new file mode 100644
index 0000000..37b07a6
--- /dev/null
+++ b/apex/statsd/framework/Android.bp
@@ -0,0 +1,67 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+filegroup {
+    name: "framework-statsd-sources",
+    srcs: [
+    "java/**/*.java",
+    ],
+    path: "java",
+}
+
+java_library {
+    name: "framework-statsd",
+    installable: true,
+    // TODO(b/146209659): Use system_current instead.
+    sdk_version: "core_platform",
+    srcs: [
+        ":framework-statsd-sources",
+    ],
+    permitted_packages: [
+        "android.app",
+        "android.util",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        // TODO(b/146230220): Use framework-system-stubs instead.
+        "android_system_stubs_current",
+    ],
+    // TODO:(b/146210774): Add apex_available field.
+}
+
+droidstubs {
+    name: "framework-statsd-stubs-docs",
+    defaults: [
+        "framework-module-stubs-defaults-publicapi"
+    ],
+    srcs: [
+        ":framework-statsd-sources",
+    ],
+    libs: [
+        "framework-all",
+    ],
+    sdk_version: "core_platform",
+}
+
+// TODO(b/146167933): Use these stubs in frameworks/base/Android.bp
+java_library {
+    name: "framework-statsd-stubs",
+    srcs: [
+        ":framework-statsd-stubs-docs",
+    ],
+    libs: [
+        "framework-all",
+    ],
+    sdk_version: "core_platform",
+}
diff --git a/core/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java
similarity index 100%
rename from core/java/android/util/StatsEvent.java
rename to apex/statsd/framework/java/android/util/StatsEvent.java
diff --git a/api/current.txt b/api/current.txt
index 2c1b066..52dd1a1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16780,16 +16780,29 @@
 package android.hardware.biometrics {
 
   public class BiometricManager {
-    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate();
+    method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int);
     field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
     field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
     field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
     field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
   }
 
+  public static interface BiometricManager.Authenticators {
+    field public static final int BIOMETRIC_STRONG = 15; // 0xf
+    field public static final int BIOMETRIC_WEAK = 255; // 0xff
+    field public static final int DEVICE_CREDENTIAL = 32768; // 0x8000
+  }
+
   public class BiometricPrompt {
     method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject, @NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
     method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public void authenticate(@NonNull android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
+    method @Nullable public int getAllowedAuthenticators();
+    method @Nullable public CharSequence getDescription();
+    method @Nullable public CharSequence getNegativeButtonText();
+    method @Nullable public CharSequence getSubtitle();
+    method @NonNull public CharSequence getTitle();
+    method public boolean isConfirmationRequired();
     field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0
     field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
     field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2
@@ -16825,9 +16838,10 @@
   public static class BiometricPrompt.Builder {
     ctor public BiometricPrompt.Builder(android.content.Context);
     method @NonNull public android.hardware.biometrics.BiometricPrompt build();
+    method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setAllowedAuthenticators(int);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setConfirmationRequired(boolean);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
-    method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
+    method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -17232,6 +17246,7 @@
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME = 12; // 0xc
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; // 0xa
+    field public static final int REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING = 15; // 0xf
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING = 4; // 0x4
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS = 5; // 0x5
@@ -22638,6 +22653,7 @@
     method public void onConfigureWindow(android.view.Window, boolean, boolean);
     method public android.view.View onCreateCandidatesView();
     method public android.view.View onCreateExtractTextView();
+    method @Nullable public android.view.inputmethod.InlineSuggestionsRequest onCreateInlineSuggestionsRequest();
     method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
     method public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
     method public android.view.View onCreateInputView();
@@ -22654,6 +22670,7 @@
     method public void onFinishInput();
     method public void onFinishInputView(boolean);
     method public void onInitializeInterface();
+    method public boolean onInlineSuggestionsResponse(@NonNull android.view.inputmethod.InlineSuggestionsResponse);
     method public boolean onKeyDown(int, android.view.KeyEvent);
     method public boolean onKeyLongPress(int, android.view.KeyEvent);
     method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
@@ -35014,6 +35031,7 @@
     method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
     method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]) throws android.os.RemoteException;
     method @Nullable public String getInterfaceDescriptor() throws android.os.RemoteException;
+    method public static int getSuggestedMaxIpcSizeBytes();
     method public boolean isBinderAlive();
     method public void linkToDeath(@NonNull android.os.IBinder.DeathRecipient, int) throws android.os.RemoteException;
     method public boolean pingBinder();
@@ -35210,6 +35228,7 @@
     method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
     method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
     method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+    method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
     method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
     method @Nullable public android.os.PersistableBundle readPersistableBundle();
     method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
@@ -35257,6 +35276,7 @@
     method public void writeNoException();
     method public void writeParcelable(@Nullable android.os.Parcelable, int);
     method public <T extends android.os.Parcelable> void writeParcelableArray(@Nullable T[], int);
+    method public void writeParcelableCreator(@NonNull android.os.Parcelable);
     method public <T extends android.os.Parcelable> void writeParcelableList(@Nullable java.util.List<T>, int);
     method public void writePersistableBundle(@Nullable android.os.PersistableBundle);
     method public void writeSerializable(@Nullable java.io.Serializable);
@@ -36007,7 +36027,8 @@
     method @WorkerThread public long getCacheSizeBytes(@NonNull java.util.UUID) throws java.io.IOException;
     method public String getMountedObbPath(String);
     method @NonNull public android.os.storage.StorageVolume getPrimaryStorageVolume();
-    method @Nullable public android.os.storage.StorageVolume getStorageVolume(java.io.File);
+    method @NonNull public java.util.List<android.os.storage.StorageVolume> getRecentStorageVolumes();
+    method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File);
     method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri);
     method @NonNull public java.util.List<android.os.storage.StorageVolume> getStorageVolumes();
     method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException;
@@ -36033,6 +36054,8 @@
     method @NonNull public android.content.Intent createOpenDocumentTreeIntent();
     method public int describeContents();
     method public String getDescription(android.content.Context);
+    method @Nullable public java.io.File getDirectory();
+    method @Nullable public String getMediaStoreVolumeName();
     method public String getState();
     method @Nullable public String getUuid();
     method public boolean isEmulated();
@@ -38384,11 +38407,11 @@
   protected static interface ContactsContract.RawContactsColumns {
     field public static final String ACCOUNT_TYPE_AND_DATA_SET = "account_type_and_data_set";
     field public static final String AGGREGATION_MODE = "aggregation_mode";
-    field public static final String BACKUP_ID = "backup_id";
+    field @Deprecated public static final String BACKUP_ID = "backup_id";
     field public static final String CONTACT_ID = "contact_id";
     field public static final String DATA_SET = "data_set";
     field public static final String DELETED = "deleted";
-    field public static final String METADATA_DIRTY = "metadata_dirty";
+    field @Deprecated public static final String METADATA_DIRTY = "metadata_dirty";
     field public static final String RAW_CONTACT_IS_READ_ONLY = "raw_contact_is_read_only";
     field public static final String RAW_CONTACT_IS_USER_PROFILE = "raw_contact_is_user_profile";
   }
@@ -38695,19 +38718,21 @@
 
   public final class MediaStore {
     ctor public MediaStore();
+    method @NonNull public static android.app.PendingIntent createDeleteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
+    method @NonNull public static android.app.PendingIntent createFavoriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+    method @NonNull public static android.app.PendingIntent createTrashRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>, boolean);
+    method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
     method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
     method public static android.net.Uri getMediaScannerUri();
     method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
+    method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
     method public static boolean getRequireOriginal(@NonNull android.net.Uri);
     method @NonNull public static String getVersion(@NonNull android.content.Context);
     method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String);
     method @NonNull public static String getVolumeName(@NonNull android.net.Uri);
     method @Deprecated @NonNull public static android.net.Uri setIncludePending(@NonNull android.net.Uri);
     method @NonNull public static android.net.Uri setRequireOriginal(@NonNull android.net.Uri);
-    method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
-    method public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
-    method public static void untrash(@NonNull android.content.Context, @NonNull android.net.Uri);
     field public static final String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
     field public static final String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
     field public static final String ACTION_REVIEW = "android.provider.action.REVIEW";
@@ -38967,6 +38992,7 @@
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
     method @Deprecated public static android.net.Uri getContentUri(String);
+    method @Deprecated @NonNull public static android.util.Size getKindSize(int);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
     method @Deprecated public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
@@ -39049,6 +39075,7 @@
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long);
     method @Deprecated public static void cancelThumbnailRequest(android.content.ContentResolver, long, long);
     method @Deprecated public static android.net.Uri getContentUri(String);
+    method @Deprecated @NonNull public static android.util.Size getKindSize(int);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options);
     method @Deprecated public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options);
     field @Deprecated public static final String DATA = "_data";
@@ -41555,7 +41582,8 @@
     method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts();
     method public int getFlags();
     method public int getId();
-    method public void writeToParcel(android.os.Parcel, int);
+    method @Nullable public android.view.inputmethod.InlineSuggestionsRequest getInlineSuggestionsRequest();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillRequest> CREATOR;
     field public static final int FLAG_COMPATIBILITY_MODE_REQUEST = 2; // 0x2
     field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1
@@ -41572,6 +41600,7 @@
   public static final class FillResponse.Builder {
     ctor public FillResponse.Builder();
     method @NonNull public android.service.autofill.FillResponse.Builder addDataset(@Nullable android.service.autofill.Dataset);
+    method @NonNull public android.service.autofill.FillResponse.Builder addInlineSuggestionSlice(@NonNull android.app.slice.Slice);
     method @NonNull public android.service.autofill.FillResponse build();
     method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
     method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
@@ -43877,6 +43906,7 @@
     field public static final String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
     field public static final String EVENT_CALL_REMOTELY_HELD = "android.telecom.event.CALL_REMOTELY_HELD";
     field public static final String EVENT_CALL_REMOTELY_UNHELD = "android.telecom.event.CALL_REMOTELY_UNHELD";
+    field public static final String EVENT_CALL_SWITCH_FAILED = "android.telecom.event.CALL_SWITCH_FAILED";
     field public static final String EVENT_MERGE_COMPLETE = "android.telecom.event.MERGE_COMPLETE";
     field public static final String EVENT_MERGE_START = "android.telecom.event.MERGE_START";
     field public static final String EVENT_ON_HOLD_TONE_END = "android.telecom.event.ON_HOLD_TONE_END";
@@ -44586,6 +44616,7 @@
     method public void notifyConfigChangedForSubId(int);
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
+    field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
     field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
     field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
     field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
@@ -44596,6 +44627,7 @@
     field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
     field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";
     field public static final String KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL = "allow_emergency_video_calls_bool";
+    field public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL = "allow_holding_video_call";
     field public static final String KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL = "allow_hold_call_during_emergency_bool";
     field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
     field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
@@ -44645,11 +44677,16 @@
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
-    field public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+    field public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING = "config_ims_mmtel_package_override_string";
+    field @Deprecated public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+    field public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING = "config_ims_rcs_package_override_string";
     field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
     field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
     field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
+    field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
     field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
+    field public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL = "data_rapid_notification_bool";
+    field public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL = "data_warning_notification_bool";
     field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
     field public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING = "default_vm_number_roaming_and_ims_unregistered_string";
@@ -45209,6 +45246,7 @@
     method public void onDataConnectionStateChanged(int);
     method public void onDataConnectionStateChanged(int, int);
     method public void onMessageWaitingIndicatorChanged(boolean);
+    method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
     method public void onServiceStateChanged(android.telephony.ServiceState);
     method @Deprecated public void onSignalStrengthChanged(int);
     method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
@@ -45223,12 +45261,23 @@
     field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000
     field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
     field public static final int LISTEN_NONE = 0; // 0x0
+    field @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
     field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
     field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
     field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
     field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
   }
 
+  public final class PreciseDataConnectionState implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLastCauseCode();
+    method @Nullable public android.net.LinkProperties getLinkProperties();
+    method public int getNetworkType();
+    method public int getState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+  }
+
   public final class RadioAccessSpecifier implements android.os.Parcelable {
     ctor public RadioAccessSpecifier(int, int[], int[]);
     method public int describeContents();
@@ -45741,6 +45790,7 @@
     field public static final int DATA_CONNECTED = 2; // 0x2
     field public static final int DATA_CONNECTING = 1; // 0x1
     field public static final int DATA_DISCONNECTED = 0; // 0x0
+    field public static final int DATA_DISCONNECTING = 4; // 0x4
     field public static final int DATA_SUSPENDED = 3; // 0x3
     field public static final int DATA_UNKNOWN = -1; // 0xffffffff
     field public static final String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
@@ -51067,6 +51117,9 @@
     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 @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets);
+    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
     method public void dispatchWindowSystemUiVisiblityChanged(int);
     method public void dispatchWindowVisibilityChanged(int);
     method @CallSuper public void draw(android.graphics.Canvas);
@@ -51247,6 +51300,7 @@
     method @android.view.ViewDebug.ExportedProperty(category="layout") public final int getWidth();
     method protected int getWindowAttachCount();
     method public android.view.WindowId getWindowId();
+    method @Nullable public android.view.WindowInsetsController getWindowInsetsController();
     method public int getWindowSystemUiVisibility();
     method public android.os.IBinder getWindowToken();
     method public int getWindowVisibility();
@@ -51585,6 +51639,7 @@
     method public void setVisibility(int);
     method @Deprecated public void setWillNotCacheDrawing(boolean);
     method public void setWillNotDraw(boolean);
+    method public void setWindowInsetsAnimationCallback(@Nullable android.view.WindowInsetsAnimationCallback);
     method public void setX(float);
     method public void setY(float);
     method public void setZ(float);
@@ -52471,6 +52526,7 @@
     method public android.transition.Transition getExitTransition();
     method protected final int getFeatures();
     method protected final int getForcedWindowFlags();
+    method @Nullable public android.view.WindowInsetsController getInsetsController();
     method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
     method protected final int getLocalFeatures();
     method public android.media.session.MediaController getMediaController();
@@ -52686,7 +52742,9 @@
     method @NonNull public android.view.WindowInsets consumeStableInsets();
     method @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
     method @Nullable public android.view.DisplayCutout getDisplayCutout();
+    method @NonNull public android.graphics.Insets getInsets(int);
     method @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
+    method @NonNull public android.graphics.Insets getMaxInsets(int) throws java.lang.IllegalArgumentException;
     method public int getStableInsetBottom();
     method public int getStableInsetLeft();
     method public int getStableInsetRight();
@@ -52705,6 +52763,7 @@
     method @NonNull public android.view.WindowInsets inset(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int);
     method public boolean isConsumed();
     method public boolean isRound();
+    method public boolean isVisible(int);
     method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(int, int, int, int);
     method @Deprecated @NonNull public android.view.WindowInsets replaceSystemWindowInsets(android.graphics.Rect);
   }
@@ -52714,11 +52773,78 @@
     ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
     method @NonNull public android.view.WindowInsets build();
     method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
+    method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
+    method @NonNull public android.view.WindowInsets.Builder setMaxInsets(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
     method @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setTappableElementInsets(@NonNull android.graphics.Insets);
+    method @NonNull public android.view.WindowInsets.Builder setVisible(int, boolean);
+  }
+
+  public static final class WindowInsets.Type {
+    method public static int all();
+    method public static int captionBar();
+    method public static int ime();
+    method public static int mandatorySystemGestures();
+    method public static int navigationBars();
+    method public static int statusBars();
+    method public static int systemBars();
+    method public static int systemGestures();
+    method public static int tappableElement();
+    method public static int windowDecor();
+  }
+
+  public interface WindowInsetsAnimationCallback {
+    method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method @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);
+  }
+
+  public static final class WindowInsetsAnimationCallback.AnimationBounds {
+    ctor public WindowInsetsAnimationCallback.AnimationBounds(@NonNull android.graphics.Insets, @NonNull android.graphics.Insets);
+    method @NonNull public android.graphics.Insets getLowerBound();
+    method @NonNull public android.graphics.Insets getUpperBound();
+    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds inset(@NonNull android.graphics.Insets);
+  }
+
+  public static final class WindowInsetsAnimationCallback.InsetsAnimation {
+    ctor public WindowInsetsAnimationCallback.InsetsAnimation(int, @Nullable android.view.animation.Interpolator, long);
+    method @FloatRange(from=0.0f, to=1.0f) public float getAlpha();
+    method public long getDurationMillis();
+    method @FloatRange(from=0.0f, to=1.0f) public float getFraction();
+    method public float getInterpolatedFraction();
+    method @Nullable public android.view.animation.Interpolator getInterpolator();
+    method public int getTypeMask();
+    method public void setDuration(long);
+    method public void setFraction(@FloatRange(from=0.0f, to=1.0f) float);
+  }
+
+  public interface WindowInsetsAnimationControlListener {
+    method public void onCancelled();
+    method public void onReady(@NonNull android.view.WindowInsetsAnimationController, int);
+  }
+
+  public interface WindowInsetsAnimationController {
+    method public void finish(boolean);
+    method public float getCurrentAlpha();
+    method @FloatRange(from=0.0f, to=1.0f) public float getCurrentFraction();
+    method @NonNull public android.graphics.Insets getCurrentInsets();
+    method @NonNull public android.graphics.Insets getHiddenStateInsets();
+    method @NonNull public android.graphics.Insets getShownStateInsets();
+    method public int getTypes();
+    method public void setInsetsAndAlpha(@Nullable android.graphics.Insets, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+  }
+
+  public interface WindowInsetsController {
+    method public default void controlInputMethodAnimation(long, @NonNull android.view.WindowInsetsAnimationControlListener);
+    method public default void hideInputMethod();
+    method public void setSystemBarsAppearance(int);
+    method public void setSystemBarsBehavior(int);
+    method public default void showInputMethod();
+    field public static final int APPEARANCE_LIGHT_NAVIGATION_BARS = 16; // 0x10
+    field public static final int APPEARANCE_LIGHT_STATUS_BARS = 8; // 0x8
   }
 
   public interface WindowManager extends android.view.ViewManager {
diff --git a/api/removed.txt b/api/removed.txt
index 1a2f434..8b30d0a 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -436,11 +436,12 @@
   }
 
   public final class MediaStore {
-    method @Deprecated @NonNull public static android.net.Uri createPending(@NonNull android.content.Context, @NonNull android.provider.MediaStore.PendingParams);
     method @Deprecated @NonNull public static java.util.Set<java.lang.String> getAllVolumeNames(@NonNull android.content.Context);
     method @Deprecated public static boolean getIncludePending(@NonNull android.net.Uri);
-    method @Deprecated @NonNull public static android.provider.MediaStore.PendingSession openPending(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @Deprecated @NonNull public static android.net.Uri setIncludeTrashed(@NonNull android.net.Uri);
+    method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
+    method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri, long);
+    method @Deprecated public static void untrash(@NonNull android.content.Context, @NonNull android.net.Uri);
   }
 
   public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
@@ -470,22 +471,6 @@
     field @Deprecated public static final String GROUP_ID = "group_id";
   }
 
-  @Deprecated public static class MediaStore.PendingParams {
-    ctor public MediaStore.PendingParams(@NonNull android.net.Uri, @NonNull String, @NonNull String);
-    method public void setDownloadUri(@Nullable android.net.Uri);
-    method public void setRefererUri(@Nullable android.net.Uri);
-    method public void setRelativePath(@Nullable String);
-  }
-
-  @Deprecated public static class MediaStore.PendingSession implements java.lang.AutoCloseable {
-    method public void abandon();
-    method public void close();
-    method public void notifyProgress(@IntRange(from=0, to=100) int);
-    method @NonNull public android.os.ParcelFileDescriptor open() throws java.io.FileNotFoundException;
-    method @NonNull public java.io.OutputStream openOutputStream() throws java.io.FileNotFoundException;
-    method @NonNull public android.net.Uri publish();
-  }
-
   public static interface MediaStore.Video.VideoColumns extends android.provider.MediaStore.MediaColumns {
     field public static final String ALBUM = "album";
     field public static final String ARTIST = "artist";
diff --git a/api/system-current.txt b/api/system-current.txt
index b449b2e..1108927 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -546,10 +546,12 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method @Deprecated public void onGetInstantAppIntentFilter(@Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
     method @Deprecated public void onGetInstantAppIntentFilter(@NonNull android.content.Intent, @Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
-    method public void onGetInstantAppIntentFilter(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+    method @Deprecated public void onGetInstantAppIntentFilter(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+    method public void onGetInstantAppIntentFilter(@NonNull android.content.pm.InstantAppRequestInfo, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
     method @Deprecated public void onGetInstantAppResolveInfo(@Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
     method @Deprecated public void onGetInstantAppResolveInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
-    method public void onGetInstantAppResolveInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+    method @Deprecated public void onGetInstantAppResolveInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, @NonNull String, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
+    method public void onGetInstantAppResolveInfo(@NonNull android.content.pm.InstantAppRequestInfo, @NonNull android.app.InstantAppResolverService.InstantAppResolutionCallback);
   }
 
   public static final class InstantAppResolverService.InstantAppResolutionCallback {
@@ -809,6 +811,14 @@
 
 }
 
+package android.app.appsearch {
+
+  public class AppSearchManagerFrameworkInitializer {
+    method public static void initialize();
+  }
+
+}
+
 package android.app.assist {
 
   public static class AssistStructure.ViewNode {
@@ -1350,6 +1360,14 @@
     field public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1; // 0xffffffff
   }
 
+  public final class BluetoothA2dpSink implements android.bluetooth.BluetoothProfile {
+    method public void finalize();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public final class BluetoothAdapter {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean connectAllEnabledProfiles(@NonNull android.bluetooth.BluetoothDevice);
@@ -1497,6 +1515,11 @@
     field public static final int REMOTE_PANU_ROLE = 2; // 0x2
   }
 
+  public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
+    method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public interface BluetoothProfile {
     field public static final int CONNECTION_POLICY_ALLOWED = 100; // 0x64
     field public static final int CONNECTION_POLICY_FORBIDDEN = 0; // 0x0
@@ -1628,6 +1651,7 @@
     field public static final String STATS_MANAGER = "stats";
     field public static final String STATUS_BAR_SERVICE = "statusbar";
     field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
+    field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
     field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
     field public static final String VR_SERVICE = "vrmanager";
     field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
@@ -1914,6 +1938,18 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstantAppIntentFilter> CREATOR;
   }
 
+  public final class InstantAppRequestInfo implements android.os.Parcelable {
+    ctor public InstantAppRequestInfo(@NonNull android.content.Intent, @Nullable int[], @NonNull android.os.UserHandle, boolean, @NonNull String);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstantAppRequestInfo> CREATOR;
+    field @Nullable public final int[] hostDigestPrefix;
+    field @NonNull public final android.content.Intent intent;
+    field public final boolean isRequesterInstantApp;
+    field @NonNull public final String token;
+    field @NonNull public final android.os.UserHandle userHandle;
+  }
+
   public final class InstantAppResolveInfo implements android.os.Parcelable {
     ctor public InstantAppResolveInfo(@NonNull android.content.pm.InstantAppResolveInfo.InstantAppDigest, @Nullable String, @Nullable java.util.List<android.content.pm.InstantAppIntentFilter>, int);
     ctor public InstantAppResolveInfo(@NonNull android.content.pm.InstantAppResolveInfo.InstantAppDigest, @Nullable String, @Nullable java.util.List<android.content.pm.InstantAppIntentFilter>, long, @Nullable android.os.Bundle);
@@ -2277,6 +2313,15 @@
 
 }
 
+package android.hardware.biometrics {
+
+  public static interface BiometricManager.Authenticators {
+    field public static final int BIOMETRIC_CONVENIENCE = 4095; // 0xfff
+    field public static final int EMPTY_SET = 0; // 0x0
+  }
+
+}
+
 package android.hardware.camera2 {
 
   public abstract class CameraDevice implements java.lang.AutoCloseable {
@@ -4158,18 +4203,20 @@
 package android.media.session {
 
   public final class MediaSessionManager {
-    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.Callback);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventDispatchedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventDispatchedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
+    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventSessionChangedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
     method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
     method @RequiresPermission(android.Manifest.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER) public void setOnVolumeKeyLongPressListener(android.media.session.MediaSessionManager.OnVolumeKeyLongPressListener, @Nullable android.os.Handler);
-    method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void unregisterCallback(@NonNull android.media.session.MediaSessionManager.Callback);
   }
 
-  public abstract static class MediaSessionManager.Callback {
-    ctor public MediaSessionManager.Callback();
-    method public abstract void onAddressedPlayerChanged(android.media.session.MediaSession.Token);
-    method public abstract void onAddressedPlayerChanged(android.content.ComponentName);
-    method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token);
-    method public abstract void onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName);
+  public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener {
+    method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token);
+  }
+
+  public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+    method public default void onMediaKeyEventSessionChanged(@NonNull String, @Nullable android.media.session.MediaSession.Token);
   }
 
   public static interface MediaSessionManager.OnMediaKeyListener {
@@ -4523,6 +4570,14 @@
     method public void onUpstreamChanged(@Nullable android.net.Network);
   }
 
+  public class InvalidPacketException extends java.lang.Exception {
+    ctor public InvalidPacketException(int);
+    field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+    field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+    field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+    field public final int error;
+  }
+
   public final class IpConfiguration implements android.os.Parcelable {
     ctor public IpConfiguration();
     ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
@@ -4573,6 +4628,15 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
   }
 
+  public class KeepalivePacketData {
+    ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
+    method @NonNull public byte[] getPacket();
+    field @NonNull public final java.net.InetAddress dstAddress;
+    field public final int dstPort;
+    field @NonNull public final java.net.InetAddress srcAddress;
+    field public final int srcPort;
+  }
+
   public class LinkAddress implements android.os.Parcelable {
     ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int);
     ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
@@ -4852,7 +4916,7 @@
     method @NonNull public java.util.List<android.net.LinkAddress> getInternalAddresses();
     method @NonNull public java.util.List<java.net.InetAddress> getInternalDhcpServers();
     method @NonNull public java.util.List<java.net.InetAddress> getInternalDnsServers();
-    method @NonNull public java.util.List<android.net.LinkAddress> getInternalSubnets();
+    method @NonNull public java.util.List<android.net.IpPrefix> getInternalSubnets();
     method @NonNull public java.util.List<android.net.ipsec.ike.IkeTrafficSelector> getOutboundTrafficSelectors();
   }
 
@@ -4921,6 +4985,7 @@
   public final class IkeSessionConfiguration {
     ctor public IkeSessionConfiguration();
     method @NonNull public String getRemoteApplicationVersion();
+    method @NonNull public java.util.List<byte[]> getRemoteVendorIDs();
     method public boolean isIkeExtensionEnabled(int);
     field public static final int EXTENSION_TYPE_FRAGMENTATION = 1; // 0x1
     field public static final int EXTENSION_TYPE_MOBIKE = 2; // 0x2
@@ -4940,9 +5005,9 @@
     ctor public IkeSessionParams.Builder();
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.IkeSaProposal);
     method @NonNull public android.net.ipsec.ike.IkeSessionParams build();
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.util.List<java.security.cert.X509Certificate>, @NonNull java.security.PrivateKey);
-    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthEap(@NonNull java.security.cert.X509Certificate, @NonNull android.net.eap.EapSessionConfig);
+    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey);
+    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.util.List<java.security.cert.X509Certificate>, @NonNull java.security.PrivateKey);
+    method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthEap(@Nullable java.security.cert.X509Certificate, @NonNull android.net.eap.EapSessionConfig);
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthPsk(@NonNull byte[]);
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setLocalIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
     method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setRemoteIdentification(@NonNull android.net.ipsec.ike.IkeIdentification);
@@ -4960,7 +5025,7 @@
   }
 
   public static class IkeSessionParams.IkeAuthDigitalSignRemoteConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
-    method @NonNull public java.security.cert.X509Certificate getRemoteCaCert();
+    method @Nullable public java.security.cert.X509Certificate getRemoteCaCert();
   }
 
   public static class IkeSessionParams.IkeAuthEapConfig extends android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig {
@@ -5024,12 +5089,10 @@
     ctor public TunnelModeChildSessionParams.Builder();
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(int);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.InetAddress, int);
+    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet4Address);
+    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalAddressRequest(@NonNull java.net.Inet6Address, int);
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDhcpServerRequest(int);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDhcpServerRequest(@NonNull java.net.InetAddress);
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDnsServerRequest(int);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalDnsServerRequest(@NonNull java.net.InetAddress);
-    method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addInternalSubnetRequest(int);
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addOutboundTrafficSelectors(@NonNull android.net.ipsec.ike.IkeTrafficSelector);
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.ChildSaProposal);
     method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams build();
@@ -5053,9 +5116,6 @@
   public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Netmask extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
   }
 
-  public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Subnet extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
-  }
-
   public static interface TunnelModeChildSessionParams.ConfigRequestIpv6Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
     method @Nullable public java.net.Inet6Address getAddress();
     method public int getPrefixLength();
@@ -5065,9 +5125,6 @@
     method @Nullable public java.net.Inet6Address getAddress();
   }
 
-  public static interface TunnelModeChildSessionParams.ConfigRequestIpv6Subnet extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest {
-  }
-
 }
 
 package android.net.ipsec.ike.exceptions {
@@ -6308,6 +6365,7 @@
   }
 
   public class Environment {
+    method @NonNull public static java.util.Collection<java.io.File> getInternalMediaDirectories();
     method @NonNull public static java.io.File getOdmDirectory();
     method @NonNull public static java.io.File getOemDirectory();
     method @NonNull public static java.io.File getProductDirectory();
@@ -7078,34 +7136,34 @@
     field public static final int STATUS_NOT_BLOCKED = 0; // 0x0
   }
 
-  public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
-    field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
-    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
-    field public static final android.net.Uri CONTENT_URI;
-    field public static final String METADATA_AUTHORITY = "com.android.contacts.metadata";
-    field public static final android.net.Uri METADATA_AUTHORITY_URI;
+  @Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
+    field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
+    field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
+    field @Deprecated public static final android.net.Uri CONTENT_URI;
+    field @Deprecated public static final String METADATA_AUTHORITY = "com.android.contacts.metadata";
+    field @Deprecated public static final android.net.Uri METADATA_AUTHORITY_URI;
   }
 
-  protected static interface ContactsContract.MetadataSyncColumns {
-    field public static final String ACCOUNT_NAME = "account_name";
-    field public static final String ACCOUNT_TYPE = "account_type";
-    field public static final String DATA = "data";
-    field public static final String DATA_SET = "data_set";
-    field public static final String DELETED = "deleted";
-    field public static final String RAW_CONTACT_BACKUP_ID = "raw_contact_backup_id";
+  @Deprecated protected static interface ContactsContract.MetadataSyncColumns {
+    field @Deprecated public static final String ACCOUNT_NAME = "account_name";
+    field @Deprecated public static final String ACCOUNT_TYPE = "account_type";
+    field @Deprecated public static final String DATA = "data";
+    field @Deprecated public static final String DATA_SET = "data_set";
+    field @Deprecated public static final String DELETED = "deleted";
+    field @Deprecated public static final String RAW_CONTACT_BACKUP_ID = "raw_contact_backup_id";
   }
 
-  public static final class ContactsContract.MetadataSyncState implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncStateColumns {
-    field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata_sync_state";
-    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata_sync_state";
-    field public static final android.net.Uri CONTENT_URI;
+  @Deprecated public static final class ContactsContract.MetadataSyncState implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncStateColumns {
+    field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata_sync_state";
+    field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata_sync_state";
+    field @Deprecated public static final android.net.Uri CONTENT_URI;
   }
 
-  protected static interface ContactsContract.MetadataSyncStateColumns {
-    field public static final String ACCOUNT_NAME = "account_name";
-    field public static final String ACCOUNT_TYPE = "account_type";
-    field public static final String DATA_SET = "data_set";
-    field public static final String STATE = "state";
+  @Deprecated protected static interface ContactsContract.MetadataSyncStateColumns {
+    field @Deprecated public static final String ACCOUNT_NAME = "account_name";
+    field @Deprecated public static final String ACCOUNT_TYPE = "account_type";
+    field @Deprecated public static final String DATA_SET = "data_set";
+    field @Deprecated public static final String STATE = "state";
   }
 
   public final class DeviceConfig {
@@ -7126,6 +7184,7 @@
     field public static final String NAMESPACE_APP_COMPAT = "app_compat";
     field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
     field public static final String NAMESPACE_AUTOFILL = "autofill";
+    field public static final String NAMESPACE_BIOMETRICS = "biometrics";
     field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
     field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
     field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
@@ -7195,8 +7254,8 @@
   }
 
   public final class MediaStore {
-    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
-    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
+    method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+    method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
   }
 
   public abstract class SearchIndexableData {
@@ -7312,6 +7371,7 @@
 
   public final class Settings {
     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";
     field public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
     field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION";
@@ -7988,6 +8048,7 @@
     field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
     field public static final String KEY_IMPORTANCE = "key_importance";
     field public static final String KEY_PEOPLE = "key_people";
+    field public static final String KEY_RANKING_SCORE = "key_ranking_score";
     field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
     field public static final String KEY_TEXT_REPLIES = "key_text_replies";
     field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -9156,6 +9217,7 @@
     method public int getSleepTimeMillis();
     method public long getTimestamp();
     method @NonNull public java.util.List<android.telephony.ModemActivityInfo.TransmitPower> getTransmitPowerInfo();
+    method public boolean isValid();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
     field public static final int TX_POWER_LEVELS = 5; // 0x5
@@ -9269,7 +9331,6 @@
     method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
     method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
-    method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
     method public void onRadioPowerStateChanged(int);
     method public void onSrvccStateChanged(int);
     method public void onVoiceActivationStateChanged(int);
@@ -9279,7 +9340,6 @@
     field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
     field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
-    field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
     field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
     field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
@@ -9305,13 +9365,12 @@
   }
 
   public final class PreciseDataConnectionState implements android.os.Parcelable {
-    method public int describeContents();
-    method @Nullable public String getDataConnectionApn();
-    method public int getDataConnectionApnTypeBitMask();
-    method public int getDataConnectionFailCause();
-    method public int getDataConnectionState();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+    method @Deprecated @NonNull public String getDataConnectionApn();
+    method @Deprecated public int getDataConnectionApnTypeBitMask();
+    method @Deprecated public int getDataConnectionFailCause();
+    method @Deprecated @Nullable public android.net.LinkProperties getDataConnectionLinkProperties();
+    method @Deprecated public int getDataConnectionNetworkType();
+    method @Deprecated public int getDataConnectionState();
   }
 
   public final class PreciseDisconnectCause {
@@ -9550,6 +9609,7 @@
   public final class SmsManager {
     method public boolean disableCellBroadcastRange(int, int, int);
     method public boolean enableCellBroadcastRange(int, int, int);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
     method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
   }
@@ -9620,9 +9680,6 @@
 
   public class TelephonyManager {
     method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionReportDefaultNetworkStatus(int, boolean);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionResetAll(int);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void carrierActionSetRadioEnabled(int, boolean);
     method public int checkCarrierPrivilegesForPackage(String);
     method public int checkCarrierPrivilegesForPackageAnyPhone(String);
     method public void dial(String);
@@ -9696,9 +9753,11 @@
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method public void requestModemActivityInfo(@NonNull android.os.ResultReceiver);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig();
     method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings();
@@ -9712,6 +9771,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRadioEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int);
@@ -10322,6 +10382,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
   }
 
+  public class ImsManager {
+    method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
+    method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+  }
+
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
     method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -10365,6 +10430,22 @@
     ctor @Deprecated public ImsMmTelManager.RegistrationCallback();
   }
 
+  public class ImsRcsManager implements android.telephony.ims.RegistrationManager {
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAvailable(int) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCapable(int, int) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerRcsAvailabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterRcsAvailabilityCallback(@NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+  }
+
+  public static class ImsRcsManager.AvailabilityCallback {
+    ctor public ImsRcsManager.AvailabilityCallback();
+    method public void onAvailabilityChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
+  }
+
   public final class ImsReasonInfo implements android.os.Parcelable {
     field public static final String EXTRA_MSG_SERVICE_NOT_AUTHORIZED = "Forbidden. Not Authorized for Service";
   }
@@ -10726,9 +10807,22 @@
 
   public class RcsFeature extends android.telephony.ims.feature.ImsFeature {
     ctor public RcsFeature();
-    method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+    method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+    method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
     method public void onFeatureReady();
     method public void onFeatureRemoved();
+    method public boolean queryCapabilityConfiguration(int, int);
+    method @NonNull public final android.telephony.ims.feature.RcsFeature.RcsImsCapabilities queryCapabilityStatus();
+  }
+
+  public static class RcsFeature.RcsImsCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+    ctor public RcsFeature.RcsImsCapabilities(int);
+    method public void addCapabilities(int);
+    method public boolean isCapable(int);
+    method public void removeCapabilities(int);
+    field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0
+    field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1
+    field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
   }
 
 }
diff --git a/api/test-current.txt b/api/test-current.txt
index 7cfc218..219258e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -697,6 +697,7 @@
   public abstract class Context {
     method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
     method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public java.io.File getCrateDir(@NonNull String);
     method public abstract android.view.Display getDisplay();
     method public abstract int getDisplayId();
     method public android.os.UserHandle getUser();
@@ -711,6 +712,7 @@
     field public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
     field public static final String ROLLBACK_SERVICE = "rollback";
     field public static final String STATUS_BAR_SERVICE = "statusbar";
+    field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
     field public static final String TEST_NETWORK_SERVICE = "test_network";
   }
 
@@ -2433,6 +2435,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
     field public static final String NAMESPACE_ANDROID = "android";
     field public static final String NAMESPACE_AUTOFILL = "autofill";
+    field public static final String NAMESPACE_BIOMETRICS = "biometrics";
     field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
     field public static final String NAMESPACE_PERMISSIONS = "permissions";
     field public static final String NAMESPACE_PRIVACY = "privacy";
@@ -2465,14 +2468,9 @@
   }
 
   public final class MediaStore {
-    method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
-    method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
-    method @NonNull public static java.io.File getVolumePath(@NonNull String) throws java.io.FileNotFoundException;
-    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public static java.util.Collection<java.io.File> getVolumeScanPaths(@NonNull String) throws java.io.FileNotFoundException;
-    method public static android.net.Uri scanFile(android.content.Context, java.io.File);
-    method public static android.net.Uri scanFileFromShell(android.content.Context, java.io.File);
-    method public static void scanVolume(android.content.Context, java.io.File);
-    method public static void waitForIdle(android.content.Context);
+    method @NonNull public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
+    method public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
+    method public static void waitForIdle(@NonNull android.content.ContentResolver);
   }
 
   public final class Settings {
@@ -2830,6 +2828,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
     field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions";
     field public static final String KEY_IMPORTANCE = "key_importance";
+    field public static final String KEY_RANKING_SCORE = "key_ranking_score";
     field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
     field public static final String KEY_TEXT_REPLIES = "key_text_replies";
     field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
@@ -3160,6 +3159,10 @@
     field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
   }
 
+  public final class PreciseDataConnectionState implements android.os.Parcelable {
+    ctor @Deprecated public PreciseDataConnectionState(int, int, int, @NonNull String, @Nullable android.net.LinkProperties, int);
+  }
+
   public class ServiceState implements android.os.Parcelable {
     method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
     method public void setCdmaSystemAndNetworkId(int, int);
@@ -3426,6 +3429,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR;
   }
 
+  public class ImsManager {
+    method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
+    method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+  }
+
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
     method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getFeatureState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>) throws android.telephony.ims.ImsException;
@@ -3469,6 +3477,22 @@
     ctor @Deprecated public ImsMmTelManager.RegistrationCallback();
   }
 
+  public class ImsRcsManager implements android.telephony.ims.RegistrationManager {
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isAvailable(int) throws android.telephony.ims.ImsException;
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public boolean isCapable(int, int) throws android.telephony.ims.ImsException;
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerRcsAvailabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterRcsAvailabilityCallback(@NonNull android.telephony.ims.ImsRcsManager.AvailabilityCallback) throws android.telephony.ims.ImsException;
+  }
+
+  public static class ImsRcsManager.AvailabilityCallback {
+    ctor public ImsRcsManager.AvailabilityCallback();
+    method public void onAvailabilityChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
+  }
+
   public class ImsService extends android.app.Service {
     ctor public ImsService();
     method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int);
@@ -3826,9 +3850,22 @@
 
   public class RcsFeature extends android.telephony.ims.feature.ImsFeature {
     ctor public RcsFeature();
-    method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+    method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
+    method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
     method public void onFeatureReady();
     method public void onFeatureRemoved();
+    method public boolean queryCapabilityConfiguration(int, int);
+    method @NonNull public final android.telephony.ims.feature.RcsFeature.RcsImsCapabilities queryCapabilityStatus();
+  }
+
+  public static class RcsFeature.RcsImsCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+    ctor public RcsFeature.RcsImsCapabilities(int);
+    method public void addCapabilities(int);
+    method public boolean isCapable(int);
+    method public void removeCapabilities(int);
+    field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0
+    field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1
+    field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
   }
 
 }
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index f3871d7..f56dd6e 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -27,12 +27,13 @@
 #include <cstring>
 #include <memory>
 
+#include <android/log.h>
+#include <android/looper.h>
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <android/looper.h>
-#include <android/log.h>
 
 #include <android-base/stringprintf.h>
 
@@ -49,6 +50,7 @@
 static struct {
     jmethodID onDeviceOpen;
     jmethodID onDeviceGetReport;
+    jmethodID onDeviceOutput;
     jmethodID onDeviceError;
 } gDeviceCallbackClassInfo;
 
@@ -64,6 +66,18 @@
     }
 }
 
+static ScopedLocalRef<jbyteArray> toJbyteArray(JNIEnv* env, const std::vector<uint8_t>& vector) {
+    ScopedLocalRef<jbyteArray> array(env, env->NewByteArray(vector.size()));
+    if (array.get() == nullptr) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+        return array;
+    }
+    static_assert(sizeof(char) == sizeof(uint8_t));
+    env->SetByteArrayRegion(array.get(), 0, vector.size(),
+                            reinterpret_cast<const signed char*>(vector.data()));
+    return array;
+}
+
 static std::string toString(const std::vector<uint8_t>& data) {
     std::string s = "";
     for (uint8_t b : data) {
@@ -101,6 +115,13 @@
     checkAndClearException(env, "onDeviceGetReport");
 }
 
+void DeviceCallback::onDeviceOutput(const std::vector<uint8_t>& data) {
+    JNIEnv* env = getJNIEnv();
+    env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput,
+                        toJbyteArray(env, data).get());
+    checkAndClearException(env, "onDeviceOutput");
+}
+
 JNIEnv* DeviceCallback::getJNIEnv() {
     JNIEnv* env;
     mJavaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
@@ -240,6 +261,12 @@
                  set_report.rnum, toString(data).c_str());
             break;
         }
+        case UHID_OUTPUT: {
+            struct uhid_output_req& output = ev.u.output;
+            std::vector<uint8_t> data(output.data, output.data + output.size);
+            mDeviceCallback->onDeviceOutput(data);
+            break;
+        }
         default: {
             LOGI("Unhandled event type: %" PRIu32, ev.type);
             break;
@@ -332,6 +359,8 @@
             env->GetMethodID(clazz, "onDeviceOpen", "()V");
     uhid::gDeviceCallbackClassInfo.onDeviceGetReport =
             env->GetMethodID(clazz, "onDeviceGetReport", "(II)V");
+    uhid::gDeviceCallbackClassInfo.onDeviceOutput =
+            env->GetMethodID(clazz, "onDeviceOutput", "([B)V");
     uhid::gDeviceCallbackClassInfo.onDeviceError =
             env->GetMethodID(clazz, "onDeviceError", "()V");
     if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL ||
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index b0471ed..93ea881 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -31,6 +31,7 @@
 
     void onDeviceOpen();
     void onDeviceGetReport(uint32_t requestId, uint8_t reportId);
+    void onDeviceOutput(const std::vector<uint8_t>& data);
     void onDeviceError();
 
 private:
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index 616d411..874604c 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -20,13 +20,16 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
-import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.os.SomeArgs;
 
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Map;
+
 public class Device {
     private static final String TAG = "HidDevice";
 
@@ -40,6 +43,7 @@
     private final DeviceHandler mHandler;
     // mFeatureReports is limited to 256 entries, because the report number is 8-bit
     private final SparseArray<byte[]> mFeatureReports;
+    private final Map<ByteBuffer, byte[]> mOutputs;
     private long mTimeToSend;
 
     private final Object mCond = new Object();
@@ -55,12 +59,13 @@
     private static native void nativeCloseDevice(long ptr);
 
     public Device(int id, String name, int vid, int pid, byte[] descriptor,
-            byte[] report, SparseArray<byte[]> featureReports) {
+            byte[] report, SparseArray<byte[]> featureReports, Map<ByteBuffer, byte[]> outputs) {
         mId = id;
         mThread = new HandlerThread("HidDeviceHandler");
         mThread.start();
         mHandler = new DeviceHandler(mThread.getLooper());
         mFeatureReports = featureReports;
+        mOutputs = outputs;
         SomeArgs args = SomeArgs.obtain();
         args.argi1 = id;
         args.argi2 = vid;
@@ -160,6 +165,11 @@
         }
 
         public void onDeviceGetReport(int requestId, int reportId) {
+            if (mFeatureReports == null) {
+                Log.e(TAG, "Received GET_REPORT request for reportId=" + reportId
+                        + ", but 'feature_reports' section is not found");
+                return;
+            }
             byte[] report = mFeatureReports.get(reportId);
 
             if (report == null) {
@@ -176,6 +186,29 @@
             mHandler.sendMessageAtTime(msg, mTimeToSend);
         }
 
+        // native callback
+        public void onDeviceOutput(byte[] data) {
+            if (mOutputs == null) {
+                Log.e(TAG, "Received OUTPUT request, but 'outputs' section is not found");
+                return;
+            }
+            byte[] response = mOutputs.get(ByteBuffer.wrap(data));
+            if (response == null) {
+                Log.i(TAG,
+                        "Requested response for output " + Arrays.toString(data) + " is not found");
+                return;
+            }
+
+            Message msg;
+            msg = mHandler.obtainMessage(MSG_SEND_REPORT, response);
+
+            // Message is set to asynchronous so it won't be blocked by synchronization
+            // barrier during UHID_OPEN. This is necessary for drivers that do
+            // UHID_OUTPUT requests during probe, and expect a response right away.
+            msg.setAsynchronous(true);
+            mHandler.sendMessageAtTime(msg, mTimeToSend);
+        }
+
         public void onDeviceError() {
             Log.e(TAG, "Device error occurred, closing /dev/uhid");
             Message msg = mHandler.obtainMessage(MSG_CLOSE_DEVICE);
diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java
index 746e372..62587a7 100644
--- a/cmds/hid/src/com/android/commands/hid/Event.java
+++ b/cmds/hid/src/com/android/commands/hid/Event.java
@@ -21,10 +21,13 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import java.io.InputStreamReader;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 public class Event {
     private static final String TAG = "HidEvent";
@@ -41,6 +44,7 @@
     private int mPid;
     private byte[] mReport;
     private SparseArray<byte[]> mFeatureReports;
+    private Map<ByteBuffer, byte[]> mOutputs;
     private int mDuration;
 
     public int getId() {
@@ -75,6 +79,10 @@
         return mFeatureReports;
     }
 
+    public Map<ByteBuffer, byte[]> getOutputs() {
+        return mOutputs;
+    }
+
     public int getDuration() {
         return mDuration;
     }
@@ -88,6 +96,7 @@
             + ", pid=" + mPid
             + ", report=" + Arrays.toString(mReport)
             + ", feature_reports=" + mFeatureReports.toString()
+            + ", outputs=" + mOutputs.toString()
             + ", duration=" + mDuration
             + "}";
     }
@@ -123,6 +132,10 @@
             mEvent.mFeatureReports = reports;
         }
 
+        public void setOutputs(Map<ByteBuffer, byte[]> outputs) {
+            mEvent.mOutputs = outputs;
+        }
+
         public void setVid(int vid) {
             mEvent.mVid = vid;
         }
@@ -199,6 +212,9 @@
                             case "feature_reports":
                                 eb.setFeatureReports(readFeatureReports());
                                 break;
+                            case "outputs":
+                                eb.setOutputs(readOutputs());
+                                break;
                             case "duration":
                                 eb.setDuration(readInt());
                                 break;
@@ -250,7 +266,7 @@
 
         private SparseArray<byte[]> readFeatureReports()
                 throws IllegalStateException, IOException {
-            SparseArray<byte[]> featureReports = new SparseArray();
+            SparseArray<byte[]> featureReports = new SparseArray<>();
             try {
                 mReader.beginArray();
                 while (mReader.hasNext()) {
@@ -276,17 +292,60 @@
                         }
                     }
                     mReader.endObject();
-                    if (data != null)
+                    if (data != null) {
                         featureReports.put(id, data);
+                    }
                 }
                 mReader.endArray();
-            } catch (IllegalStateException|NumberFormatException e) {
+            } catch (IllegalStateException | NumberFormatException e) {
                 consumeRemainingElements();
                 mReader.endArray();
                 throw new IllegalStateException("Encountered malformed data.", e);
-            } finally {
-                return featureReports;
             }
+            return featureReports;
+        }
+
+        private Map<ByteBuffer, byte[]> readOutputs()
+                throws IllegalStateException, IOException {
+            Map<ByteBuffer, byte[]> outputs = new HashMap<>();
+
+            try {
+                mReader.beginArray();
+                while (mReader.hasNext()) {
+                    byte[] output = null;
+                    byte[] response = null;
+                    mReader.beginObject();
+                    while (mReader.hasNext()) {
+                        String name = mReader.nextName();
+                        switch (name) {
+                            case "description":
+                                // Description is only used to keep track of the output responses
+                                mReader.nextString();
+                                break;
+                            case "output":
+                                output = readData();
+                                break;
+                            case "response":
+                                response = readData();
+                                break;
+                            default:
+                                consumeRemainingElements();
+                                mReader.endObject();
+                                throw new IllegalStateException("Invalid key in outputs: " + name);
+                        }
+                    }
+                    mReader.endObject();
+                    if (output != null) {
+                        outputs.put(ByteBuffer.wrap(output), response);
+                    }
+                }
+                mReader.endArray();
+            } catch (IllegalStateException | NumberFormatException e) {
+                consumeRemainingElements();
+                mReader.endArray();
+                throw new IllegalStateException("Encountered malformed data.", e);
+            }
+            return outputs;
         }
 
         private void consumeRemainingElements() throws IOException {
@@ -296,10 +355,6 @@
         }
     }
 
-    private static void error(String msg) {
-        error(msg, null);
-    }
-
     private static void error(String msg, Exception e) {
         System.out.println(msg);
         Log.e(TAG, msg);
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 54ac1b0..0ee2cc4 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -16,22 +16,17 @@
 
 package com.android.commands.hid;
 
-import android.util.JsonReader;
-import android.util.JsonToken;
 import android.util.Log;
 import android.util.SparseArray;
 
 import libcore.io.IoUtils;
 
-import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.IOException;
 import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
 
 public class Hid {
     private static final String TAG = "HID";
@@ -119,7 +114,7 @@
         }
         int id = e.getId();
         Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(),
-                e.getDescriptor(), e.getReport(), e.getFeatureReports());
+                e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs());
         mDevices.append(id, d);
     }
 
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
index 6c3d197..eb2b98a 100644
--- a/cmds/incident/main.cpp
+++ b/cmds/incident/main.cpp
@@ -375,7 +375,7 @@
     if (destination == DEST_STDOUT) {
         // Call into the service
         sp<StatusListener> listener(new StatusListener());
-        status = service->reportIncidentToStream(args, listener, writeEnd);
+        status = service->reportIncidentToStream(args, listener, std::move(writeEnd));
 
         if (!status.isOk()) {
             fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
@@ -388,7 +388,7 @@
     } else if (destination == DEST_DUMPSTATE) {
         // Call into the service
         sp<StatusListener> listener(new StatusListener());
-        status = service->reportIncidentToDumpstate(writeEnd, listener);
+        status = service->reportIncidentToDumpstate(std::move(writeEnd), listener);
         if (!status.isOk()) {
             fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
             return 1;
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 999936b..cfd77c2 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -279,7 +279,7 @@
 
 Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args,
                                                const sp<IIncidentReportStatusListener>& listener,
-                                               const unique_fd& stream) {
+                                               unique_fd stream) {
     IncidentReportArgs argsCopy(args);
 
     // Streaming reports can not also be broadcast.
@@ -306,7 +306,7 @@
     return Status::ok();
 }
 
-Status IncidentService::reportIncidentToDumpstate(const unique_fd& stream,
+Status IncidentService::reportIncidentToDumpstate(unique_fd stream,
         const sp<IIncidentReportStatusListener>& listener) {
     uid_t caller = IPCThreadState::self()->getCallingUid();
     if (caller != AID_ROOT && caller != AID_SHELL) {
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index fb013d0..b2c7f23 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -121,9 +121,9 @@
 
     virtual Status reportIncidentToStream(const IncidentReportArgs& args,
                                           const sp<IIncidentReportStatusListener>& listener,
-                                          const unique_fd& stream);
+                                          unique_fd stream);
 
-    virtual Status reportIncidentToDumpstate(const unique_fd& stream,
+    virtual Status reportIncidentToDumpstate(unique_fd stream,
             const sp<IIncidentReportStatusListener>& listener);
 
     virtual Status systemRunning();
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 484f823..afff614 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -146,6 +146,7 @@
         "libprotoutil",
         "libservices",
         "libstatslog",
+        "libstatsmetadata",
         "libstatssocket",
         "libsysutils",
         "libtimestats_proto",
@@ -153,6 +154,63 @@
     ],
 }
 
+// ================
+// libstatsmetadata
+// ================
+
+genrule {
+    name: "atoms_info.h",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --atomsInfoHeader $(genDir)/atoms_info.h",
+    out: [
+        "atoms_info.h",
+    ],
+}
+
+genrule {
+    name: "atoms_info.cpp",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --atomsInfoCpp $(genDir)/atoms_info.cpp",
+    out: [
+        "atoms_info.cpp",
+    ],
+}
+
+cc_library_shared {
+    name: "libstatsmetadata",
+    host_supported: true,
+    generated_sources: [
+        "atoms_info.cpp",
+    ],
+    generated_headers: [
+        "atoms_info.h",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    export_generated_headers: [
+        "atoms_info.h",
+    ],
+    shared_libs: [
+        "libcutils",
+        "libstatslog",
+    ],
+    target: {
+        android: {
+            shared_libs: [
+                "libutils",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libutils",
+            ],
+        },
+    },
+}
+
+
 // =========
 // statsd
 // =========
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 84a0607..a545fc5 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -18,8 +18,8 @@
 #include "Log.h"
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
+#include "atoms_info.h"
 #include "math.h"
-#include "statslog.h"
 
 namespace android {
 namespace os {
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 98d41c2..3481814 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -27,6 +27,7 @@
 #include <utils/SystemClock.h>
 
 #include "android-base/stringprintf.h"
+#include "atoms_info.h"
 #include "external/StatsPullerManager.h"
 #include "guardrail/StatsdStats.h"
 #include "metrics/CountMetricProducer.h"
diff --git a/cmds/statsd/src/external/PowerStatsPuller.cpp b/cmds/statsd/src/external/PowerStatsPuller.cpp
index b142cac..dc69b78 100644
--- a/cmds/statsd/src/external/PowerStatsPuller.cpp
+++ b/cmds/statsd/src/external/PowerStatsPuller.cpp
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include "PowerStatsPuller.h"
+#include "statslog.h"
 #include "stats_log_util.h"
 
 using android::hardware::hidl_vec;
diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp
index 53fa630..031c437 100644
--- a/cmds/statsd/src/external/puller_util.cpp
+++ b/cmds/statsd/src/external/puller_util.cpp
@@ -18,8 +18,8 @@
 #include "Log.h"
 
 #include "StatsPullerManager.h"
+#include "atoms_info.h"
 #include "puller_util.h"
-#include "statslog.h"
 
 namespace android {
 namespace os {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 23d2ace..564b9ee 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -16,7 +16,7 @@
 #pragma once
 
 #include "config/ConfigKey.h"
-#include "statslog.h"
+#include "atoms_info.h"
 
 #include <gtest/gtest_prod.h>
 #include <log/log_time.h>
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 464cec3..088f607 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -23,6 +23,7 @@
 #include <utils/SystemClock.h>
 
 #include "CountMetricProducer.h"
+#include "atoms_info.h"
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
 #include "guardrail/StatsdStats.h"
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 9131802..2ad8217 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -22,6 +22,7 @@
 
 #include <inttypes.h>
 
+#include "atoms_info.h"
 #include "condition/CombinationConditionTracker.h"
 #include "condition/SimpleConditionTracker.h"
 #include "condition/StateConditionTracker.h"
@@ -36,7 +37,6 @@
 #include "metrics/ValueMetricProducer.h"
 #include "state/StateManager.h"
 #include "stats_util.h"
-#include "statslog.h"
 
 using std::set;
 using std::string;
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index e65325a..7453370 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -15,7 +15,7 @@
  */
 #pragma once
 
-#include <statslog.h>
+#include <atoms_info.h>
 #include <utils/RefBase.h>
 #include "HashableDimensionKey.h"
 #include "logd/LogEvent.h"
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 0a86363..f3e9433 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -19,9 +19,9 @@
 #include <android/util/ProtoOutputStream.h>
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
+#include "atoms_info.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "guardrail/StatsdStats.h"
-#include "statslog.h"
 
 namespace android {
 namespace os {
diff --git a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
index e91fb0d..6abdfa3 100644
--- a/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/GpuStatsPuller_test.cpp
@@ -24,6 +24,7 @@
 #include <log/log.h>
 
 #include "src/external/GpuStatsPuller.h"
+#include "statslog.h"
 
 #ifdef __ANDROID__
 
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
index 5c9636f..5b7a30d 100644
--- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "SurfaceflingerStatsPuller_test"
 
 #include "src/external/SurfaceflingerStatsPuller.h"
+#include "statslog.h"
 
 #include <gtest/gtest.h>
 #include <log/log.h>
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
index 266ea35..6730828 100644
--- a/cmds/statsd/tests/external/puller_util_test.cpp
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -17,6 +17,7 @@
 #include <gtest/gtest.h>
 #include <stdio.h>
 #include <vector>
+#include "statslog.h"
 #include "../metrics/metrics_test_helper.h"
 
 #ifdef __ANDROID__
diff --git a/cmds/svc/src/com/android/commands/svc/DataCommand.java b/cmds/svc/src/com/android/commands/svc/DataCommand.java
index 35510cf..b4dbd1d 100644
--- a/cmds/svc/src/com/android/commands/svc/DataCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/DataCommand.java
@@ -16,12 +16,16 @@
 
 package com.android.commands.svc;
 
-import android.os.ServiceManager;
-import android.os.RemoteException;
-import android.content.Context;
-import com.android.internal.telephony.ITelephony;
-
+/**
+ * @deprecated Please use adb shell cmd phone data enabled/disable instead.
+ */
+@Deprecated
 public class DataCommand extends Svc.Command {
+
+    private static final String DECPRECATED_MESSAGE =
+            "adb shell svc data enable/disable is deprecated;"
+            + "please use adb shell cmd phone data enable/disable instead.";
+
     public DataCommand() {
         super("data");
     }
@@ -33,36 +37,10 @@
     public String longHelp() {
         return shortHelp() + "\n"
                 + "\n"
-                + "usage: svc data [enable|disable]\n"
-                + "         Turn mobile data on or off.\n\n";
+                + DECPRECATED_MESSAGE;
     }
 
     public void run(String[] args) {
-        boolean validCommand = false;
-        if (args.length >= 2) {
-            boolean flag = false;
-            if ("enable".equals(args[1])) {
-                flag = true;
-                validCommand = true;
-            } else if ("disable".equals(args[1])) {
-                flag = false;
-                validCommand = true;
-            }
-            if (validCommand) {
-                ITelephony phoneMgr
-                        = ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
-                try {
-                    if (flag) {
-                        phoneMgr.enableDataConnectivity();
-                    } else
-                        phoneMgr.disableDataConnectivity();
-                }
-                catch (RemoteException e) {
-                    System.err.println("Mobile data operation failed: " + e);
-                }
-                return;
-            }
-        }
-        System.err.println(longHelp());
+        System.err.println(DECPRECATED_MESSAGE);
     }
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 8b62e2f..d82b151 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -17,6 +17,8 @@
 package android.accessibilityservice;
 
 
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
 import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
@@ -58,6 +60,8 @@
 
     /** @hide */
     @IntDef(prefix = { "GESTURE_" }, value = {
+            GESTURE_DOUBLE_TAP,
+            GESTURE_DOUBLE_TAP_AND_HOLD,
             GESTURE_SWIPE_UP,
             GESTURE_SWIPE_UP_AND_LEFT,
             GESTURE_SWIPE_UP_AND_DOWN,
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 47fdcde..0f619c8 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -300,6 +300,18 @@
     public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
 
     /**
+     * The user has performed a double tap gesture on the touch screen.
+     * @hide
+     */
+    public static final int GESTURE_DOUBLE_TAP = 17;
+
+    /**
+     * The user has performed a double tap and hold gesture on the touch screen.
+     * @hide
+     */
+    public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18;
+
+    /**
      * The {@link Intent} that must be declared as handled by the service.
      */
     public static final String SERVICE_INTERFACE =
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index f4e465a..0f10c39 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1943,35 +1943,6 @@
     }
 
     /**
-     * @hide
-     * Removes the shared account.
-     * @param account the account to remove
-     * @param user the user to remove the account from
-     * @return
-     */
-    public boolean removeSharedAccount(final Account account, UserHandle user) {
-        try {
-            boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
-            return val;
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * @hide
-     * @param user
-     * @return
-     */
-    public Account[] getSharedAccounts(UserHandle user) {
-        try {
-            return mService.getSharedAccountsAsUser(user.getIdentifier());
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Confirms that the user knows the password for an account to make extra
      * sure they are the owner of the account.  The user-entered password can
      * be supplied directly, otherwise the authenticator for this account type
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 4cf0a20..0127138 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -80,14 +80,11 @@
         String authTokenType);
 
     /* Shared accounts */
-    Account[] getSharedAccountsAsUser(int userId);
-    boolean removeSharedAccountAsUser(in Account account, int userId);
     void addSharedAccountsFromParentUser(int parentUserId, int userId, String opPackageName);
 
     /* Account renaming. */
     void renameAccount(in IAccountManagerResponse response, in Account accountToRename, String newName);
     String getPreviousName(in Account account);
-    boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
 
     /* Add account in two steps. */
     void startAddAccountSession(in IAccountManagerResponse response, String accountType,
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 577272e..1e3b950 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -766,9 +766,19 @@
 
     private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:";
     private static final String AUTO_FILL_AUTH_WHO_PREFIX = "@android:autoFillAuth:";
-
     private static final String KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME = "com.android.systemui";
 
+    private static final int LOG_AM_ON_CREATE_CALLED = 30057;
+    private static final int LOG_AM_ON_START_CALLED = 30059;
+    private static final int LOG_AM_ON_RESUME_CALLED = 30022;
+    private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
+    private static final int LOG_AM_ON_STOP_CALLED = 30049;
+    private static final int LOG_AM_ON_RESTART_CALLED = 30058;
+    private static final int LOG_AM_ON_DESTROY_CALLED = 30060;
+    private static final int LOG_AM_ON_ACTIVITY_RESULT_CALLED = 30062;
+    private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064;
+    private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065;
+
     private static class ManagedDialog {
         Dialog mDialog;
         Bundle mArgs;
@@ -2439,8 +2449,11 @@
      * {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
      */
     public final void requestShowKeyboardShortcuts() {
+        final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+                getResources().getString(
+                        com.android.internal.R.string.config_systemUIServiceComponent));
         Intent intent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
-        intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME);
+        intent.setPackage(sysuiComponent.getPackageName());
         sendBroadcastAsUser(intent, Process.myUserHandle());
     }
 
@@ -2448,8 +2461,11 @@
      * Dismiss the Keyboard Shortcuts screen.
      */
     public final void dismissKeyboardShortcutsHelper() {
+        final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+                getResources().getString(
+                        com.android.internal.R.string.config_systemUIServiceComponent));
         Intent intent = new Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS);
-        intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME);
+        intent.setPackage(sysuiComponent.getPackageName());
         sendBroadcastAsUser(intent, Process.myUserHandle());
     }
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 590b3db..795f51a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4417,4 +4417,18 @@
             }
         }
     }
+
+    /**
+     * Get packages of bugreport-whitelisted apps to handle a bug report.
+     *
+     * @return packages of bugreport-whitelisted apps to handle a bug report.
+     * @hide
+     */
+    public List<String> getBugreportWhitelistedPackages() {
+        try {
+            return getService().getBugreportWhitelistedPackages();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 46f88d5..155e93f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -99,6 +99,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteOrder;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -251,6 +252,8 @@
     @GuardedBy("mSync")
     private File mFilesDir;
     @GuardedBy("mSync")
+    private File mCratesDir;
+    @GuardedBy("mSync")
     private File mNoBackupFilesDir;
     @GuardedBy("mSync")
     private File mCacheDir;
@@ -702,6 +705,24 @@
     }
 
     @Override
+    public File getCrateDir(@NonNull String crateId) {
+        Preconditions.checkArgument(FileUtils.isValidExtFilename(crateId), "invalidated crateId");
+        final Path cratesRootPath = getDataDir().toPath().resolve("crates");
+        final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
+                .toAbsolutePath().normalize();
+
+        synchronized (mSync) {
+            if (mCratesDir == null) {
+                mCratesDir = cratesRootPath.toFile();
+            }
+            ensurePrivateDirExists(mCratesDir);
+        }
+
+        File cratedDir = absoluteNormalizedCratePath.toFile();
+        return ensurePrivateDirExists(cratedDir);
+    }
+
+    @Override
     public File getNoBackupFilesDir() {
         synchronized (mSync) {
             if (mNoBackupFilesDir == null) {
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 80c9ba2..eb50581 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1314,8 +1314,8 @@
 
         // TODO: DownloadProvider.update() should take care of updating corresponding
         // MediaProvider entries.
-        MediaStore.scanFile(context, before);
-        MediaStore.scanFile(context, after);
+        MediaStore.scanFile(mResolver, before);
+        MediaStore.scanFile(mResolver, after);
 
         final ContentValues values = new ContentValues();
         values.put(Downloads.Impl.COLUMN_TITLE, displayName);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index aa8a302..112bd30 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -388,6 +388,7 @@
     void requestFullBugReport();
     void requestRemoteBugReport();
     boolean launchBugReportHandlerApp();
+    List<String> getBugreportWhitelistedPackages();
 
     @UnsupportedAppUsage
     Intent getIntentForIntentSender(in IIntentSender sender);
diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl
index 7318762..8618fbb 100644
--- a/core/java/android/app/IInstantAppResolver.aidl
+++ b/core/java/android/app/IInstantAppResolver.aidl
@@ -16,15 +16,13 @@
 
 package android.app;
 
-import android.content.Intent;
+import android.content.pm.InstantAppRequestInfo;
 import android.os.IRemoteCallback;
 
 /** @hide */
 oneway interface IInstantAppResolver {
-    void getInstantAppResolveInfoList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
-            int userId, String token, int sequence, IRemoteCallback callback);
+    void getInstantAppResolveInfoList(in InstantAppRequestInfo request, int sequence,
+            IRemoteCallback callback);
 
-    void getInstantAppIntentFilterList(in Intent sanitizedIntent, in int[] hostDigestPrefix,
-            int userId, String token, IRemoteCallback callback);
-
+    void getInstantAppIntentFilterList(in InstantAppRequestInfo request, IRemoteCallback callback);
 }
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index a7be542..a413c60 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -21,6 +21,7 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.InstantAppRequestInfo;
 import android.content.pm.InstantAppResolveInfo;
 import android.os.Build;
 import android.os.Bundle;
@@ -59,8 +60,9 @@
      * Called to retrieve resolve info for instant applications immediately.
      *
      * @param digestPrefix The hash prefix of the instant app's domain.
-     * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
-     *             String, InstantAppResolutionCallback)}.
+     *
+     * @deprecated Should implement {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo,
+     *             InstantAppResolutionCallback)}
      */
     @Deprecated
     public void onGetInstantAppResolveInfo(@Nullable int[] digestPrefix, @NonNull String token,
@@ -73,8 +75,9 @@
      * sources.
      *
      * @param digestPrefix The hash prefix of the instant app's domain.
-     * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
-     *             String, InstantAppResolutionCallback)}.
+     *
+     * @deprecated Should implement {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+     *             InstantAppResolutionCallback)}
      */
     @Deprecated
     public void onGetInstantAppIntentFilter(@Nullable int[] digestPrefix, @NonNull String token,
@@ -103,8 +106,8 @@
      *
      * @see InstantAppResolveInfo
      *
-     * @deprecated Should implement {@link #onGetInstantAppResolveInfo(Intent, int[], UserHandle,
-     *             String, InstantAppResolutionCallback)}.
+     * @deprecated Should implement {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo,
+     *             InstantAppResolutionCallback)}
      */
     @Deprecated
     public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
@@ -134,8 +137,8 @@
      *              {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
      * @param callback The {@link InstantAppResolutionCallback} to provide results to.
      *
-     * @deprecated Should implement {@link #onGetInstantAppIntentFilter(Intent, int[], UserHandle,
-     *             String, InstantAppResolutionCallback)}.
+     * @deprecated Should implement {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+     *             InstantAppResolutionCallback)}
      */
     @Deprecated
     public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
@@ -170,7 +173,11 @@
      * @param callback The {@link InstantAppResolutionCallback} to provide results to.
      *
      * @see InstantAppResolveInfo
+     *
+     * @deprecated Should implement {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo,
+     *             InstantAppResolutionCallback
      */
+    @Deprecated
     public void onGetInstantAppResolveInfo(@NonNull Intent sanitizedIntent,
             @Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
             @NonNull String token, @NonNull InstantAppResolutionCallback callback) {
@@ -193,7 +200,11 @@
      *              Intent, int[], UserHandle, String, InstantAppResolutionCallback)} and provided
      *              to the currently visible installer via {@link Intent#EXTRA_INSTANT_APP_TOKEN}.
      * @param callback The {@link InstantAppResolutionCallback} to provide results to.
+     *
+     * @deprecated Should implement {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+     *             InstantAppResolutionCallback)}
      */
+    @Deprecated
     public void onGetInstantAppIntentFilter(@NonNull Intent sanitizedIntent,
             @Nullable int[] hostDigestPrefix, @NonNull UserHandle userHandle,
             @NonNull String token, @NonNull InstantAppResolutionCallback callback) {
@@ -202,6 +213,41 @@
     }
 
     /**
+     * Called to retrieve resolve info for instant applications immediately. The response will be
+     * ignored if not provided within a reasonable time. {@link InstantAppResolveInfo}s provided
+     * in response to this method may be partial to request a second phase of resolution which will
+     * result in a subsequent call to {@link #onGetInstantAppIntentFilter(InstantAppRequestInfo,
+     * InstantAppResolutionCallback)}
+     *
+     * @param request The parameters for this resolution request
+     * @param callback The {@link InstantAppResolutionCallback} to provide results to.
+     *
+     * @see InstantAppResolveInfo
+     */
+    public void onGetInstantAppResolveInfo(@NonNull InstantAppRequestInfo request,
+            @NonNull InstantAppResolutionCallback callback) {
+        // If not overridden, forward to the old method.
+        onGetInstantAppResolveInfo(request.intent, request.hostDigestPrefix, request.userHandle,
+                request.token, callback);
+    }
+
+    /**
+     * Called to retrieve intent filters for potentially matching instant applications. Unlike
+     * {@link #onGetInstantAppResolveInfo(InstantAppRequestInfo, InstantAppResolutionCallback)},
+     * the response may take as long as necessary to respond. All {@link InstantAppResolveInfo}s
+     * provided in response to this method must be completely populated.
+     *
+     * @param request The parameters for this resolution request
+     * @param callback The {@link InstantAppResolutionCallback} to provide results to.
+     */
+    public void onGetInstantAppIntentFilter(@NonNull InstantAppRequestInfo request,
+            @NonNull InstantAppResolutionCallback callback) {
+        // If not overridden, forward to the old method.
+        onGetInstantAppIntentFilter(request.intent, request.hostDigestPrefix, request.userHandle,
+                request.token, callback);
+    }
+
+    /**
      * Returns a {@link Looper} to perform service operations on.
      */
     Looper getLooper() {
@@ -218,35 +264,29 @@
     public final IBinder onBind(Intent intent) {
         return new IInstantAppResolver.Stub() {
             @Override
-            public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix,
-                    int userId, String token, int sequence, IRemoteCallback callback) {
+            public void getInstantAppResolveInfoList(InstantAppRequestInfo request, int sequence,
+                    IRemoteCallback callback) {
                 if (DEBUG_INSTANT) {
-                    Slog.v(TAG, "[" + token + "] Phase1 called; posting");
+                    Slog.v(TAG, "[" + request.token + "] Phase1 called; posting");
                 }
                 final SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callback;
-                args.arg2 = digestPrefix;
-                args.arg3 = userId;
-                args.arg4 = token;
-                args.arg5 = sanitizedIntent;
-                mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO,
-                        sequence, 0, args).sendToTarget();
+                args.arg1 = request;
+                args.arg2 = callback;
+                mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence,
+                        0, args).sendToTarget();
             }
 
             @Override
-            public void getInstantAppIntentFilterList(Intent sanitizedIntent,
-                    int[] digestPrefix, int userId, String token, IRemoteCallback callback) {
+            public void getInstantAppIntentFilterList(InstantAppRequestInfo request,
+                    IRemoteCallback callback) {
                 if (DEBUG_INSTANT) {
-                    Slog.v(TAG, "[" + token + "] Phase2 called; posting");
+                    Slog.v(TAG, "[" + request.token + "] Phase2 called; posting");
                 }
                 final SomeArgs args = SomeArgs.obtain();
-                args.arg1 = callback;
-                args.arg2 = digestPrefix;
-                args.arg3 = userId;
-                args.arg4 = token;
-                args.arg5 = sanitizedIntent;
-                mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER,
-                        args).sendToTarget();
+                args.arg1 = request;
+                args.arg2 = callback;
+                mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, args)
+                        .sendToTarget();
             }
         };
     }
@@ -287,36 +327,33 @@
             switch (action) {
                 case MSG_GET_INSTANT_APP_RESOLVE_INFO: {
                     final SomeArgs args = (SomeArgs) message.obj;
-                    final IRemoteCallback callback = (IRemoteCallback) args.arg1;
-                    final int[] digestPrefix = (int[]) args.arg2;
-                    final int userId = (int) args.arg3;
-                    final String token = (String) args.arg4;
-                    final Intent intent = (Intent) args.arg5;
+                    final InstantAppRequestInfo request = (InstantAppRequestInfo) args.arg1;
+                    final IRemoteCallback callback = (IRemoteCallback) args.arg2;
+                    args.recycle();
                     final int sequence = message.arg1;
                     if (DEBUG_INSTANT) {
-                        Slog.d(TAG, "[" + token + "] Phase1 request;"
-                                + " prefix: " + Arrays.toString(digestPrefix)
-                                + ", userId: " + userId);
+                        Slog.d(TAG, "[" + request.token + "] Phase1 request;"
+                                + " prefix: " + Arrays.toString(request.hostDigestPrefix)
+                                + ", userId: " + request.userHandle.getIdentifier());
                     }
-                    onGetInstantAppResolveInfo(intent, digestPrefix, UserHandle.of(userId), token,
+                    onGetInstantAppResolveInfo(request,
                             new InstantAppResolutionCallback(sequence, callback));
                 } break;
 
                 case MSG_GET_INSTANT_APP_INTENT_FILTER: {
                     final SomeArgs args = (SomeArgs) message.obj;
-                    final IRemoteCallback callback = (IRemoteCallback) args.arg1;
-                    final int[] digestPrefix = (int[]) args.arg2;
-                    final int userId = (int) args.arg3;
-                    final String token = (String) args.arg4;
-                    final Intent intent = (Intent) args.arg5;
+                    final InstantAppRequestInfo request = (InstantAppRequestInfo) args.arg1;
+                    final IRemoteCallback callback = (IRemoteCallback) args.arg2;
+                    args.recycle();
                     if (DEBUG_INSTANT) {
-                        Slog.d(TAG, "[" + token + "] Phase2 request;"
-                                + " prefix: " + Arrays.toString(digestPrefix)
-                                + ", userId: " + userId);
+                        Slog.d(TAG, "[" + request.token + "] Phase2 request;"
+                                + " prefix: " + Arrays.toString(request.hostDigestPrefix)
+                                + ", userId: " + request.userHandle.getIdentifier());
                     }
-                    onGetInstantAppIntentFilter(intent, digestPrefix, UserHandle.of(userId), token,
+                    onGetInstantAppIntentFilter(request,
                             new InstantAppResolutionCallback(-1 /*sequence*/, callback));
-                } break;
+                }
+                break;
 
                 default: {
                     throw new IllegalArgumentException("Unknown message: " + action);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index bd948ec5..ce21db3 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -152,6 +152,8 @@
 import android.os.health.SystemHealthManager;
 import android.os.image.DynamicSystemManager;
 import android.os.image.IDynamicSystemService;
+import android.os.incremental.IIncrementalManagerNative;
+import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.permission.PermissionControllerManager;
 import android.permission.PermissionManager;
@@ -1145,6 +1147,14 @@
                         return new TimeZoneDetector();
                     }});
 
+        registerService(Context.TELEPHONY_IMS_SERVICE, android.telephony.ims.ImsManager.class,
+                new CachedServiceFetcher<android.telephony.ims.ImsManager>() {
+                    @Override
+                    public android.telephony.ims.ImsManager createService(ContextImpl ctx) {
+                        return new android.telephony.ims.ImsManager(ctx.getOuterContext());
+                    }
+                });
+
         registerService(Context.PERMISSION_SERVICE, PermissionManager.class,
                 new CachedServiceFetcher<PermissionManager>() {
                     @Override
@@ -1217,6 +1227,20 @@
                                 Context.DATA_LOADER_MANAGER_SERVICE);
                         return new DataLoaderManager(IDataLoaderManager.Stub.asInterface(b));
                     }});
+        //TODO(b/136132412): refactor this: 1) merge IIncrementalManager.aidl and
+        //IIncrementalManagerNative.aidl, 2) implement the binder interface in
+        //IncrementalManagerService.java, 3) use JNI to call native functions
+        registerService(Context.INCREMENTAL_SERVICE, IncrementalManager.class,
+                new CachedServiceFetcher<IncrementalManager>() {
+                    @Override
+                    public IncrementalManager createService(ContextImpl ctx) {
+                        IBinder b = ServiceManager.getService(Context.INCREMENTAL_SERVICE);
+                        if (b == null) {
+                            return null;
+                        }
+                        return new IncrementalManager(
+                                IIncrementalManagerNative.Stub.asInterface(b));
+                    }});
         //CHECKSTYLE:ON IndentationCheck
 
         sInitializing = true;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6e7ead1..7332978 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1349,6 +1349,9 @@
      * Broadcast action: send when any policy admin changes a policy.
      * This is generally used to find out when a new policy is in effect.
      *
+     * If the profile owner of an organization-owned managed profile changes some user
+     * restriction explicitly on the parent user, this broadcast will <em>not</em> be
+     * sent to the parent user.
      * @hide
      */
     @UnsupportedAppUsage
@@ -7958,18 +7961,23 @@
      * <p>
      * The calling device admin must be a profile or device owner; if it is not, a security
      * exception will be thrown.
+     * <p>
+     * The profile owner of an organization-owned managed profile may invoke this method on
+     * the {@link DevicePolicyManager} instance it obtained from
+     * {@link #getParentProfileInstance(ComponentName)}, for enforcing device-wide restrictions.
+     * <p>
+     * See the constants in {@link android.os.UserManager} for the list of restrictions that can
+     * be enforced device-wide.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @param key The key of the restriction. See the constants in {@link android.os.UserManager}
-     *            for the list of keys.
+     * @param key   The key of the restriction.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     public void addUserRestriction(@NonNull ComponentName admin,
             @UserManager.UserRestrictionKey String key) {
-        throwIfParentInstance("addUserRestriction");
         if (mService != null) {
             try {
-                mService.setUserRestriction(admin, key, true);
+                mService.setUserRestriction(admin, key, true, mParentInstance);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -7981,18 +7989,22 @@
      * <p>
      * The calling device admin must be a profile or device owner; if it is not, a security
      * exception will be thrown.
+     * <p>
+     * The profile owner of an organization-owned managed profile may invoke this method on
+     * the {@link DevicePolicyManager} instance it obtained from
+     * {@link #getParentProfileInstance(ComponentName)}, for clearing device-wide restrictions.
+     * <p>
+     * See the constants in {@link android.os.UserManager} for the list of restrictions.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @param key The key of the restriction. See the constants in {@link android.os.UserManager}
-     *            for the list of keys.
+     * @param key   The key of the restriction.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     public void clearUserRestriction(@NonNull ComponentName admin,
             @UserManager.UserRestrictionKey String key) {
-        throwIfParentInstance("clearUserRestriction");
         if (mService != null) {
             try {
-                mService.setUserRestriction(admin, key, false);
+                mService.setUserRestriction(admin, key, false, mParentInstance);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -8006,16 +8018,20 @@
      * The target user may have more restrictions set by the system or other device owner / profile
      * owner. To get all the user restrictions currently set, use
      * {@link UserManager#getUserRestrictions()}.
+     * <p>
+     * The profile owner of an organization-owned managed profile may invoke this method on
+     * the {@link DevicePolicyManager} instance it obtained from
+     * {@link #getParentProfileInstance(ComponentName)}, for retrieving device-wide restrictions
+     * it previously set with {@link #addUserRestriction(ComponentName, String)}.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     public @NonNull Bundle getUserRestrictions(@NonNull ComponentName admin) {
-        throwIfParentInstance("getUserRestrictions");
         Bundle ret = null;
         if (mService != null) {
             try {
-                ret = mService.getUserRestrictions(admin);
+                ret = mService.getUserRestrictions(admin, mParentInstance);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 949e8ab..9c82ff6 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -208,8 +208,8 @@
     void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
     ComponentName getRestrictionsProvider(int userHandle);
 
-    void setUserRestriction(in ComponentName who, in String key, boolean enable);
-    Bundle getUserRestrictions(in ComponentName who);
+    void setUserRestriction(in ComponentName who, in String key, boolean enable, boolean parent);
+    Bundle getUserRestrictions(in ComponentName who, boolean parent);
     void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
     void clearCrossProfileIntentFilters(in ComponentName admin);
 
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index 5e530ee..bd1eea5 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -515,8 +515,8 @@
     public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
             String callingPackage) {
         Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);
-        intent.setComponent(new ComponentName("com.android.systemui",
-                "com.android.systemui.SlicePermissionActivity"));
+        intent.setComponent(ComponentName.unflattenFromString(context.getResources().getString(
+                com.android.internal.R.string.config_slicePermissionComponent)));
         intent.putExtra(EXTRA_BIND_URI, sliceUri);
         intent.putExtra(EXTRA_PKG, callingPackage);
         intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index cf33676..c6957e1 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -17,7 +17,9 @@
 package android.bluetooth;
 
 import android.Manifest;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -39,6 +41,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class BluetoothA2dpSink implements BluetoothProfile {
     private static final String TAG = "BluetoothA2dpSink";
     private static final boolean DBG = true;
@@ -59,71 +62,14 @@
      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
+     * @hide
      */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
 
-    /**
-     * Intent used to broadcast the change in the Playing state of the A2DP Sink
-     * profile.
-     *
-     * <p>This intent will have 3 extras:
-     * <ul>
-     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
-     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
-     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
-     * </ul>
-     *
-     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
-     * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     */
-    public static final String ACTION_PLAYING_STATE_CHANGED =
-            "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED";
-
-    /**
-     * A2DP sink device is streaming music. This state can be one of
-     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
-     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
-     */
-    public static final int STATE_PLAYING = 10;
-
-    /**
-     * A2DP sink device is NOT streaming music. This state can be one of
-     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
-     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
-     */
-    public static final int STATE_NOT_PLAYING = 11;
-
-    /**
-     * Intent used to broadcast the change in the Playing state of the A2DP Sink
-     * profile.
-     *
-     * <p>This intent will have 3 extras:
-     * <ul>
-     * <li> {@link #EXTRA_AUDIO_CONFIG} - The audio configuration for the remote device. </li>
-     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
-     * </ul>
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     */
-    public static final String ACTION_AUDIO_CONFIG_CHANGED =
-            "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED";
-
-    /**
-     * Extra for the {@link #ACTION_AUDIO_CONFIG_CHANGED} intent.
-     *
-     * This extra represents the current audio configuration of the A2DP source device.
-     * {@see BluetoothAudioConfig}
-     */
-    public static final String EXTRA_AUDIO_CONFIG =
-            "android.bluetooth.a2dp-sink.profile.extra.AUDIO_CONFIG";
-
     private BluetoothAdapter mAdapter;
     private final BluetoothProfileConnector<IBluetoothA2dpSink> mProfileConnector =
             new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK,
@@ -170,13 +116,11 @@
      * the state. Users can get the connection state of the profile
      * from this intent.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission.
-     *
      * @param device Remote Bluetooth Device
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothA2dpSink service = getService();
@@ -210,14 +154,12 @@
      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
      * two scenarios.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission.
-     *
      * @param device Remote Bluetooth Device
      * @return false on immediate error, true otherwise
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothA2dpSink service = getService();
@@ -235,6 +177,8 @@
 
     /**
      * {@inheritDoc}
+     *
+     * @hide
      */
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
@@ -254,6 +198,8 @@
 
     /**
      * {@inheritDoc}
+     *
+     * @hide
      */
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -273,6 +219,8 @@
 
     /**
      * {@inheritDoc}
+     *
+     * @hide
      */
     @Override
     public int getConnectionState(BluetoothDevice device) {
@@ -300,6 +248,8 @@
      * @return audio configuration for the device, or null
      *
      * {@see BluetoothAudioConfig}
+     *
+     * @hide
      */
     public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
         if (VDBG) log("getAudioConfig(" + device + ")");
@@ -347,7 +297,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 IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
@@ -395,7 +345,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-    public int getConnectionPolicy(BluetoothDevice device) {
+    public int getConnectionPolicy(@Nullable BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
@@ -411,13 +361,16 @@
     }
 
     /**
-     * Check if A2DP profile is streaming music.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     * Check if audio is playing on the bluetooth device (A2DP profile is streaming music).
      *
      * @param device BluetoothDevice device
+     * @return true if audio is playing (A2dp is streaming music), false otherwise
+     *
+     * @hide
      */
-    public boolean isA2dpPlaying(BluetoothDevice device) {
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean isAudioPlaying(@Nullable BluetoothDevice device) {
         final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
@@ -448,9 +401,9 @@
                 return "connected";
             case STATE_DISCONNECTING:
                 return "disconnecting";
-            case STATE_PLAYING:
+            case BluetoothA2dp.STATE_PLAYING:
                 return "playing";
-            case STATE_NOT_PLAYING:
+            case BluetoothA2dp.STATE_NOT_PLAYING:
                 return "not playing";
             default:
                 return "<unknown state " + state + ">";
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index d94c657..df02896 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -16,7 +16,10 @@
 
 package android.bluetooth;
 
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -54,6 +57,7 @@
  *
  * @hide
  */
+@SystemApi
 public class BluetoothPbap implements BluetoothProfile {
 
     private static final String TAG = "BluetoothPbap";
@@ -75,7 +79,11 @@
      *  {@link BluetoothProfile#STATE_DISCONNECTING}.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
      * receive.
+     *
+     * @hide
      */
+    @SuppressLint("ActionValue")
+    @SystemApi
     @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
@@ -85,33 +93,16 @@
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
 
+    /** @hide */
     public static final int RESULT_FAILURE = 0;
+    /** @hide */
     public static final int RESULT_SUCCESS = 1;
-    /** Connection canceled before completion. */
-    public static final int RESULT_CANCELED = 2;
-
     /**
-     * An interface for notifying Bluetooth PCE IPC clients when they have
-     * been connected to the BluetoothPbap service.
+     * Connection canceled before completion.
+     *
+     * @hide
      */
-    public interface ServiceListener {
-        /**
-         * Called to notify the client when this proxy object has been
-         * connected to the BluetoothPbap service. Clients must wait for
-         * this callback before making IPC calls on the BluetoothPbap
-         * service.
-         */
-        public void onServiceConnected(BluetoothPbap proxy);
-
-        /**
-         * Called to notify the client that this proxy object has been
-         * disconnected from the BluetoothPbap service. Clients must not
-         * make IPC calls on the BluetoothPbap service after this callback.
-         * This callback will currently only occur if the application hosting
-         * the BluetoothPbap service, but may be called more often in future.
-         */
-        public void onServiceDisconnected();
-    }
+    public static final int RESULT_CANCELED = 2;
 
     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
             new IBluetoothStateChangeCallback.Stub() {
@@ -127,6 +118,8 @@
 
     /**
      * Create a BluetoothPbap proxy object.
+     *
+     * @hide
      */
     public BluetoothPbap(Context context, ServiceListener l) {
         mContext = context;
@@ -181,6 +174,7 @@
         }
     }
 
+    /** @hide */
     protected void finalize() throws Throwable {
         try {
             close();
@@ -194,6 +188,8 @@
      * Other public functions of BluetoothPbap will return default error
      * results once close() has been called. Multiple invocations of close()
      * are ok.
+     *
+     * @hide
      */
     public synchronized void close() {
         IBluetoothManager mgr = mAdapter.getBluetoothManager();
@@ -210,6 +206,8 @@
 
     /**
      * {@inheritDoc}
+     *
+     * @hide
      */
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
@@ -229,17 +227,22 @@
 
     /**
      * {@inheritDoc}
+     *
+     * @hide
      */
+    @SystemApi
     @Override
-    public int getConnectionState(BluetoothDevice device) {
+    public int getConnectionState(@Nullable BluetoothDevice device) {
         log("getConnectionState: device=" + device);
-        final IBluetoothPbap service = mService;
-        if (service == null) {
-            Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.STATE_DISCONNECTED;
-        }
         try {
-            return service.getConnectionState(device);
+            final IBluetoothPbap service = mService;
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.getConnectionState(device);
+            }
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return BluetoothProfile.STATE_DISCONNECTED;
         } catch (RemoteException e) {
             Log.e(TAG, e.toString());
         }
@@ -248,6 +251,8 @@
 
     /**
      * {@inheritDoc}
+     *
+     * @hide
      */
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -266,22 +271,12 @@
     }
 
     /**
-     * Returns true if the specified Bluetooth device is connected (does not
-     * include connecting). Returns false if not connected, or if this proxy
-     * object is not currently connected to the Pbap service.
-     */
-    // TODO: This is currently being used by SettingsLib and internal app.
-    public boolean isConnected(BluetoothDevice device) {
-        return getConnectionState(device) == BluetoothAdapter.STATE_CONNECTED;
-    }
-
-    /**
      * Disconnects the current Pbap client (PCE). Currently this call blocks,
      * it may soon be made asynchronous. Returns false if this proxy object is
      * not currently connected to the Pbap service.
+     *
+     * @hide
      */
-    // TODO: This is currently being used by SettingsLib and will be used in the future.
-    // TODO: Must specify target device. Implement this in the service.
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
         log("disconnect()");
@@ -304,7 +299,7 @@
             log("Proxy object connected");
             mService = IBluetoothPbap.Stub.asInterface(service);
             if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothPbap.this);
+                mServiceListener.onServiceConnected(BluetoothProfile.PBAP, BluetoothPbap.this);
             }
         }
 
@@ -312,11 +307,23 @@
             log("Proxy object disconnected");
             doUnbind();
             if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected();
+                mServiceListener.onServiceDisconnected(BluetoothProfile.PBAP);
             }
         }
     };
 
+    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/content/Context.java b/core/java/android/content/Context.java
index 85027d9..24b5061 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1060,6 +1060,31 @@
     public abstract File getFilesDir();
 
     /**
+     * Returns the absolute path to the directory that is related to the crate on the filesystem.
+     * <p>
+     *     The crateId require a validated file name. It can't contain any "..", ".",
+     *     {@link File#separatorChar} etc..
+     * </p>
+     * <p>
+     * The returned path may change over time if the calling app is moved to an
+     * adopted storage device, so only relative paths should be persisted.
+     * </p>
+     * <p>
+     * No additional permissions are required for the calling app to read or
+     * write files under the returned path.
+     *</p>
+     *
+     * @param crateId the relative validated file name under {@link Context#getDataDir()}/crates
+     * @return the crate directory file.
+     * @hide
+     */
+    @NonNull
+    @TestApi
+    public File getCrateDir(@NonNull String crateId) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Returns the absolute path to the directory on the filesystem similar to
      * {@link #getFilesDir()}. The difference is that files placed under this
      * directory will be excluded from automatic backup to remote storage. See
@@ -4336,6 +4361,15 @@
     public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
 
     /**
+     * Use with {@link #getSystemService(String)} to access the
+     * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService}.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
+
+    /**
      * Official published name of the (internal) permission service.
      *
      * @see #getSystemService(String)
@@ -4916,6 +4950,8 @@
      * {@link android.telephony.ims.ImsManager}.
      * @hide
      */
+    @SystemApi
+    @TestApi
     public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
 
     /**
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index d6442e2..d1b5135 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -252,6 +252,16 @@
         return mBase.getFilesDir();
     }
 
+    /**
+     * {@inheritDoc Context#getCrateDir()}
+     * @hide
+     */
+    @NonNull
+    @Override
+    public File getCrateDir(@NonNull String cratedId) {
+        return mBase.getCrateDir(cratedId);
+    }
+
     @Override
     public File getNoBackupFilesDir() {
         return mBase.getNoBackupFilesDir();
diff --git a/core/java/android/content/pm/AuxiliaryResolveInfo.java b/core/java/android/content/pm/AuxiliaryResolveInfo.java
index 202df50..7d07e1d 100644
--- a/core/java/android/content/pm/AuxiliaryResolveInfo.java
+++ b/core/java/android/content/pm/AuxiliaryResolveInfo.java
@@ -46,17 +46,21 @@
     public final Intent failureIntent;
     /** The matching filters for this resolve info. */
     public final List<AuxiliaryFilter> filters;
+    /** Stored {@link InstantAppRequest#hostDigestPrefixSecure} to prevent re-generation */
+    public final int[] hostDigestPrefixSecure;
 
     /** Create a response for installing an instant application. */
     public AuxiliaryResolveInfo(@NonNull String token,
             boolean needsPhase2,
             @Nullable Intent failureIntent,
-            @Nullable List<AuxiliaryFilter> filters) {
+            @Nullable List<AuxiliaryFilter> filters,
+            @Nullable int[] hostDigestPrefix) {
         this.token = token;
         this.needsPhaseTwo = needsPhase2;
         this.failureIntent = failureIntent;
         this.filters = filters;
         this.installFailureActivity = null;
+        this.hostDigestPrefixSecure = hostDigestPrefix;
     }
 
     /** Create a response for installing a split on demand. */
@@ -69,6 +73,7 @@
         this.token = null;
         this.needsPhaseTwo = false;
         this.failureIntent = failureIntent;
+        this.hostDigestPrefixSecure = null;
     }
 
     /** Create a response for installing a split on demand. */
@@ -126,4 +131,4 @@
                     + ", splitName='" + splitName + '\'' + '}';
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/BaseParceledListSlice.java b/core/java/android/content/pm/BaseParceledListSlice.java
index 4178309..ffbca16 100644
--- a/core/java/android/content/pm/BaseParceledListSlice.java
+++ b/core/java/android/content/pm/BaseParceledListSlice.java
@@ -47,7 +47,7 @@
      * TODO get this number from somewhere else. For now set it to a quarter of
      * the 1MB limit.
      */
-    private static final int MAX_IPC_SIZE = IBinder.MAX_IPC_SIZE;
+    private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
 
     private final List<T> mList;
 
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 0b3c765..e954635 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -39,6 +39,9 @@
     void transfer(in String packageName, in IntentSender statusReceiver);
     void abandon();
 
+    void addFile(String name, long lengthBytes, in byte[] metadata);
+    void removeFile(String name);
+
     boolean isMultiPackage();
     int[] getChildSessionIds();
     void addChildSessionId(in int sessionId);
@@ -46,5 +49,4 @@
     int getParentSessionId();
 
     boolean isStaged();
-    void addFile(in String name, long size, in byte[] metadata);
 }
diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java
index 361d4e4..f692db1 100644
--- a/core/java/android/content/pm/InstantAppRequest.java
+++ b/core/java/android/content/pm/InstantAppRequest.java
@@ -16,9 +16,10 @@
 
 package android.content.pm;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Intent;
 import android.os.Bundle;
-import android.text.TextUtils;
 
 /**
  * Information needed to make an instant application resolution request.
@@ -34,32 +35,40 @@
     public final String resolvedType;
     /** The name of the package requesting the instant application */
     public final String callingPackage;
+    /** Whether or not the requesting package was an instant app */
+    public final boolean isRequesterInstantApp;
     /** ID of the user requesting the instant application */
     public final int userId;
     /**
      * Optional extra bundle provided by the source application to the installer for additional
-     * verification. */
+     * verification.
+     */
     public final Bundle verificationBundle;
     /** Whether resolution occurs because an application is starting */
     public final boolean resolveForStart;
-    /** The instant app digest for this request */
-    public final InstantAppResolveInfo.InstantAppDigest digest;
+    /**
+     * The hash prefix of an instant app's domain or null if no host is defined.
+     * Secure version that should be carried through for external use.
+     */
+    @Nullable
+    public final int[] hostDigestPrefixSecure;
+    /** A unique identifier */
+    @NonNull
+    public final String token;
 
     public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
-            String resolvedType, String callingPackage, int userId, Bundle verificationBundle,
-            boolean resolveForStart) {
+            String resolvedType, String callingPackage, boolean isRequesterInstantApp,
+            int userId, Bundle verificationBundle, boolean resolveForStart,
+            @Nullable int[] hostDigestPrefixSecure, @NonNull String token) {
         this.responseObj = responseObj;
         this.origIntent = origIntent;
         this.resolvedType = resolvedType;
         this.callingPackage = callingPackage;
+        this.isRequesterInstantApp = isRequesterInstantApp;
         this.userId = userId;
         this.verificationBundle = verificationBundle;
         this.resolveForStart = resolveForStart;
-        if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) {
-            digest = new InstantAppResolveInfo.InstantAppDigest(
-                    origIntent.getData().getHost(), 5 /*maxDigests*/);
-        } else {
-            digest = InstantAppResolveInfo.InstantAppDigest.UNDEFINED;
-        }
+        this.hostDigestPrefixSecure = hostDigestPrefixSecure;
+        this.token = token;
     }
 }
diff --git a/core/java/android/content/pm/InstantAppRequestInfo.aidl b/core/java/android/content/pm/InstantAppRequestInfo.aidl
new file mode 100644
index 0000000..0f94220
--- /dev/null
+++ b/core/java/android/content/pm/InstantAppRequestInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable InstantAppRequestInfo;
diff --git a/core/java/android/content/pm/InstantAppRequestInfo.java b/core/java/android/content/pm/InstantAppRequestInfo.java
new file mode 100644
index 0000000..83d5536
--- /dev/null
+++ b/core/java/android/content/pm/InstantAppRequestInfo.java
@@ -0,0 +1,188 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Information exposed to {@link android.app.InstantAppResolverService} to complete
+ * an instant application resolution request.
+ * @hide
+ */
+@SystemApi
+@DataClass(genParcelable = true, genConstructor = true, genAidl = true)
+public final class InstantAppRequestInfo implements Parcelable {
+
+    /**
+     * The sanitized {@link Intent} used for resolution. A sanitized Intent is an intent with
+     * potential PII removed from the original intent. Fields removed include extras and the
+     * host + path of the data, if defined.
+     */
+    @NonNull
+    public final Intent intent;
+
+    /** The hash prefix of the instant app's domain or null if no host is defined. */
+    @Nullable
+    public final int[] hostDigestPrefix;
+
+    /** The user requesting the instant application */
+    @NonNull
+    public final UserHandle userHandle;
+
+    /** Whether or not the requesting package was an instant app itself */
+    public final boolean isRequesterInstantApp;
+
+    /** A unique identifier */
+    @NonNull
+    public final String token;
+
+
+
+    // Code below generated by codegen v1.0.13.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/InstantAppRequestInfo.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new InstantAppRequestInfo.
+     *
+     * @param intent
+     *   The sanitized {@link Intent} used for resolution. A sanitized Intent is an intent with
+     *   potential PII removed from the original intent. Fields removed include extras and the
+     *   host + path of the data, if defined.
+     * @param hostDigestPrefix
+     *   The hash prefix of the instant app's domain or null if no host is defined.
+     * @param userHandle
+     *   The user requesting the instant application
+     * @param isRequesterInstantApp
+     *   Whether or not the requesting package was an instant app itself
+     * @param token
+     *   A unique identifier
+     */
+    @DataClass.Generated.Member
+    public InstantAppRequestInfo(
+            @NonNull Intent intent,
+            @Nullable int[] hostDigestPrefix,
+            @NonNull UserHandle userHandle,
+            boolean isRequesterInstantApp,
+            @NonNull String token) {
+        this.intent = intent;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, intent);
+        this.hostDigestPrefix = hostDigestPrefix;
+        this.userHandle = userHandle;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, userHandle);
+        this.isRequesterInstantApp = isRequesterInstantApp;
+        this.token = token;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, token);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (isRequesterInstantApp) flg |= 0x8;
+        if (hostDigestPrefix != null) flg |= 0x2;
+        dest.writeByte(flg);
+        dest.writeTypedObject(intent, flags);
+        if (hostDigestPrefix != null) dest.writeIntArray(hostDigestPrefix);
+        dest.writeTypedObject(userHandle, flags);
+        dest.writeString(token);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ InstantAppRequestInfo(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean _isRequesterInstantApp = (flg & 0x8) != 0;
+        Intent _intent = (Intent) in.readTypedObject(Intent.CREATOR);
+        int[] _hostDigestPrefix = (flg & 0x2) == 0 ? null : in.createIntArray();
+        UserHandle _userHandle = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
+        String _token = in.readString();
+
+        this.intent = _intent;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, intent);
+        this.hostDigestPrefix = _hostDigestPrefix;
+        this.userHandle = _userHandle;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, userHandle);
+        this.isRequesterInstantApp = _isRequesterInstantApp;
+        this.token = _token;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, token);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<InstantAppRequestInfo> CREATOR
+            = new Parcelable.Creator<InstantAppRequestInfo>() {
+        @Override
+        public InstantAppRequestInfo[] newArray(int size) {
+            return new InstantAppRequestInfo[size];
+        }
+
+        @Override
+        public InstantAppRequestInfo createFromParcel(@NonNull android.os.Parcel in) {
+            return new InstantAppRequestInfo(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1574373347443L,
+            codegenVersion = "1.0.13",
+            sourceFile = "frameworks/base/core/java/android/content/pm/InstantAppRequestInfo.java",
+            inputSignatures = "public final @android.annotation.NonNull android.content.Intent intent\npublic final @android.annotation.Nullable int[] hostDigestPrefix\npublic final @android.annotation.NonNull android.os.UserHandle userHandle\npublic final  boolean isRequesterInstantApp\npublic final @android.annotation.NonNull java.lang.String token\nclass InstantAppRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=true, genAidl=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 8f51435..898631e 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -231,6 +231,15 @@
     public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
 
     /**
+     * Streaming installation pending.
+     * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
+     *
+     * @see #EXTRA_SESSION_ID
+     * {@hide}
+     */
+    public static final int STATUS_PENDING_STREAMING = -2;
+
+    /**
      * User action is currently required to proceed. You can launch the intent
      * activity described by {@link Intent#EXTRA_INTENT} to involve the user and
      * continue.
@@ -1059,13 +1068,56 @@
             }
         }
 
+
+        /**
+         * Adds a file to session. On commit this file will be pulled from dataLoader.
+         *
+         * @param name arbitrary, unique name of your choosing to identify the
+         *            APK being written. You can open a file again for
+         *            additional writes (such as after a reboot) by using the
+         *            same name. This name is only meaningful within the context
+         *            of a single install session.
+         * @param lengthBytes total size of the file being written.
+         *            The system may clear various caches as needed to allocate
+         *            this space.
+         * @param metadata additional info use by dataLoader to pull data for the file.
+         * @throws SecurityException if called after the session has been
+         *             sealed or abandoned
+         * @throws IllegalStateException if called for non-callback session
+         * {@hide}
+         */
+        public void addFile(@NonNull String name, long lengthBytes, @NonNull byte[] metadata) {
+            try {
+                mSession.addFile(name, lengthBytes, metadata);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Removes a file.
+         *
+         * @param name name of a file, e.g. split.
+         * @throws SecurityException if called after the session has been
+         *             sealed or abandoned
+         * @throws IllegalStateException if called for non-callback session
+         * {@hide}
+         */
+        public void removeFile(@NonNull String name) {
+            try {
+                mSession.removeFile(name);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         /**
          * Attempt to commit everything staged in this session. This may require
          * user intervention, and so it may not happen immediately. The final
          * result of the commit will be reported through the given callback.
          * <p>
-         * Once this method is called, the session is sealed and no additional
-         * mutations may be performed on the session. If the device reboots
+         * Once this method is called, the session is sealed and no additional mutations may be
+         * performed on the session. In case of device reboot or data loader transient failure
          * before the session has been finalized, you may commit the session again.
          * <p>
          * If the installer is the device owner or the affiliated profile owner, there will be no
@@ -1220,27 +1272,6 @@
         }
 
         /**
-         * Configure files for an installation session.
-         *
-         * Currently only for Incremental installation session. Once this method is called,
-         * the files and their paths, as specified in the parameters, will be created and properly
-         * configured in the Incremental File System.
-         *
-         * TODO(b/136132412): update this and InstallationFile class with latest API design.
-         *
-         * @throws IllegalStateException if {@link SessionParams#incrementalParams} is null.
-         *
-         * @hide
-         */
-        public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
-            try {
-                mSession.addFile(name, size, metadata);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-
-        /**
          * Release this session object. You can open the session again if it
          * hasn't been finalized.
          */
@@ -1429,6 +1460,9 @@
         public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
         /** {@hide} */
         public IncrementalDataLoaderParams incrementalParams;
+        /** TODO(b/146080380): add a class name to make it fully compatible with ComponentName.
+         * {@hide} */
+        public String dataLoaderPackageName;
 
         /**
          * Construct parameters for a new package install session.
@@ -1468,6 +1502,7 @@
                 incrementalParams = new IncrementalDataLoaderParams(
                         dataLoaderParamsParcel);
             }
+            dataLoaderPackageName = source.readString();
         }
 
         /** {@hide} */
@@ -1492,6 +1527,7 @@
             ret.isStaged = isStaged;
             ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
             ret.incrementalParams = incrementalParams;
+            ret.dataLoaderPackageName = dataLoaderPackageName;
             return ret;
         }
 
@@ -1831,6 +1867,20 @@
             this.incrementalParams = incrementalParams;
         }
 
+        /**
+         * Set the data provider params for the session.
+         * This also switches installation into callback mode and disallow direct writes into
+         * staging folder.
+         * TODO(b/146080380): unify dataprovider params with Incremental.
+         *
+         * @param dataLoaderPackageName name of the dataLoader package
+         * {@hide}
+         */
+        @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+        public void setDataLoaderPackageName(String dataLoaderPackageName) {
+            this.dataLoaderPackageName = dataLoaderPackageName;
+        }
+
         /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
@@ -1851,6 +1901,7 @@
             pw.printPair("isMultiPackage", isMultiPackage);
             pw.printPair("isStaged", isStaged);
             pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
+            pw.printPair("dataLoaderPackageName", dataLoaderPackageName);
             pw.println();
         }
 
@@ -1885,6 +1936,7 @@
             } else {
                 dest.writeParcelable(null, flags);
             }
+            dest.writeString(dataLoaderPackageName);
         }
 
         public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 94af541..0d1f404 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1513,16 +1513,6 @@
     public static final int DELETE_DONT_KILL_APP = 0x00000008;
 
     /**
-     * Flag parameter for {@link #deletePackage} to indicate that any
-     * contributed media should also be deleted during this uninstall. The
-     * meaning of "contributed" means it won't automatically be deleted when the
-     * app is uninstalled.
-     *
-     * @hide
-     */
-    public static final int DELETE_CONTRIBUTED_MEDIA = 0x00000010;
-
-    /**
      * Flag parameter for {@link #deletePackage} to indicate that package deletion
      * should be chatty.
      *
diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java
index 515185e..35df474 100644
--- a/core/java/android/content/pm/parsing/AndroidPackage.java
+++ b/core/java/android/content/pm/parsing/AndroidPackage.java
@@ -229,6 +229,11 @@
 
     String getOverlayTargetName();
 
+    /**
+     * Map of overlayable name to actor name.
+     */
+    Map<String, String> getOverlayables();
+
     // TODO(b/135203078): Does this and getAppInfoPackageName have to be separate methods?
     //  The refactor makes them the same value with no known consequences, so should be redundant.
     String getPackageName();
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index edbf73a0..7732316 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -52,6 +52,7 @@
 import android.content.pm.split.DefaultSplitAssetLoader;
 import android.content.pm.split.SplitAssetDependencyLoader;
 import android.content.pm.split.SplitAssetLoader;
+import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -92,6 +93,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /** @hide */
@@ -287,8 +289,23 @@
                                 + result.getErrorMessage());
             }
 
-            return result.getResultAndNull()
-                    .setVolumeUuid(volumeUuid)
+            ParsingPackage pkg = result.getResultAndNull();
+            ApkAssets apkAssets = assets.getApkAssets()[0];
+            if (apkAssets.definesOverlayable()) {
+                SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
+                int size = packageNames.size();
+                for (int index = 0; index < size; index++) {
+                    String packageName = packageNames.get(index);
+                    Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
+                    if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
+                        for (String overlayable : overlayableToActor.keySet()) {
+                            pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable));
+                        }
+                    }
+                }
+            }
+
+            return pkg.setVolumeUuid(volumeUuid)
                     .setApplicationVolumeUuid(volumeUuid)
                     .setSigningDetails(SigningDetails.UNKNOWN);
         } catch (PackageParserException e) {
@@ -2321,7 +2338,7 @@
                         parsingPackage.setProfileableByShell(true);
                     }
                     XmlUtils.skipCurrentTag(parser);
-
+                    break;
                 default:
                     if (!PackageParser.RIGID_PARSER) {
                         Slog.w(TAG, "Unknown element under <application>: " + tagName
diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java
index 377279e..0e736d5 100644
--- a/core/java/android/content/pm/parsing/PackageImpl.java
+++ b/core/java/android/content/pm/parsing/PackageImpl.java
@@ -18,6 +18,8 @@
 
 import static android.os.Build.VERSION_CODES.DONUT;
 
+import static java.util.Collections.emptyMap;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
@@ -55,11 +57,13 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.server.SystemConfig;
 
 import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -126,6 +130,7 @@
     private String overlayCategory;
     private int overlayPriority;
     private boolean overlayIsStatic;
+    private Map<String, String> overlayables = emptyMap();
 
     private String staticSharedLibName;
     private long staticSharedLibVersion;
@@ -475,7 +480,7 @@
 
     @Override
     public Map<String, ArraySet<PublicKey>> getKeySetMapping() {
-        return keySetMapping == null ? Collections.emptyMap() : keySetMapping;
+        return keySetMapping == null ? emptyMap() : keySetMapping;
     }
 
     @Override
@@ -773,6 +778,13 @@
     }
 
     @Override
+    public ParsingPackage addOverlayable(String overlayableName, String actorName) {
+        this.overlayables = CollectionUtils.add(this.overlayables,
+                TextUtils.safeIntern(overlayableName), TextUtils.safeIntern(actorName));
+        return this;
+    }
+
+    @Override
     public PackageImpl addAdoptPermission(String adoptPermission) {
         this.adoptPermissions = ArrayUtils.add(this.adoptPermissions, adoptPermission);
         return this;
@@ -2125,6 +2137,11 @@
     }
 
     @Override
+    public Map<String, String> getOverlayables() {
+        return overlayables;
+    }
+
+    @Override
     public boolean isOverlayIsStatic() {
         return overlayIsStatic;
     }
@@ -2291,7 +2308,7 @@
         appInfo.metaData = appMetaData;
         appInfo.minAspectRatio = minAspectRatio;
         appInfo.minSdkVersion = minSdkVersion;
-        appInfo.name = name;
+        appInfo.name = className;
         if (appInfo.name != null) {
             appInfo.name = appInfo.name.trim();
         }
@@ -2957,6 +2974,7 @@
         dest.writeString(this.overlayCategory);
         dest.writeInt(this.overlayPriority);
         dest.writeBoolean(this.overlayIsStatic);
+        dest.writeMap(this.overlayables);
         dest.writeString(this.staticSharedLibName);
         dest.writeLong(this.staticSharedLibVersion);
         dest.writeStringList(this.libraryNames);
@@ -3100,6 +3118,8 @@
         this.overlayCategory = in.readString();
         this.overlayPriority = in.readInt();
         this.overlayIsStatic = in.readBoolean();
+        this.overlayables = new HashMap<>();
+        in.readMap(overlayables, boot);
         this.staticSharedLibName = TextUtils.safeIntern(in.readString());
         this.staticSharedLibVersion = in.readLong();
         this.libraryNames = in.createStringArrayList();
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 43c1f6e..aff1b2e 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -62,6 +62,8 @@
 
     ParsingPackage addOriginalPackage(String originalPackage);
 
+    ParsingPackage addOverlayable(String overlayableName, String actorName);
+
     ParsingPackage addPermission(ParsedPermission permission);
 
     ParsingPackage addPermissionGroup(ParsedPermissionGroup permissionGroup);
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 48d8867..39c1dac 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -823,6 +823,7 @@
         switch (token.toUpperCase(Locale.US)) {
             case "COLLATE": case "ASC": case "DESC":
             case "BINARY": case "RTRIM": case "NOCASE":
+            case "LOCALIZED": case "UNICODE":
                 return;
         }
         throw new IllegalArgumentException("Invalid token " + token);
diff --git a/core/java/android/hardware/biometrics/Authenticator.java b/core/java/android/hardware/biometrics/Authenticator.java
deleted file mode 100644
index 6d7e748..0000000
--- a/core/java/android/hardware/biometrics/Authenticator.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.biometrics;
-
-/**
- * Type of authenticators defined on a granularity that the BiometricManager / BiometricPrompt
- * supports.
- * @hide
- */
-public class Authenticator {
-
-    /**
-     * Device credential, e.g. Pin/Pattern/Password.
-     */
-    public static final int TYPE_CREDENTIAL = 1 << 0;
-    /**
-     * Encompasses all biometrics on the device, e.g. Fingerprint/Iris/Face.
-     */
-    public static final int TYPE_BIOMETRIC = 1 << 1;
-
-}
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 191516b..d28b7c5 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -16,8 +16,9 @@
 
 package android.hardware.biometrics;
 
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
 import android.annotation.UnsupportedAppUsage;
-import android.app.KeyguardManager;
 
 
 /**
@@ -126,8 +127,8 @@
 
     /**
      * The device does not have pin, pattern, or password set up. See
-     * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} and
-     * {@link KeyguardManager#isDeviceSecure()}
+     * {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)},
+     * {@link Authenticators#DEVICE_CREDENTIAL}, and {@link BiometricManager#canAuthenticate(int)}.
      */
     int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
 
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index f17b3ae..7e1506f 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -21,6 +21,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -65,6 +66,77 @@
             BIOMETRIC_ERROR_NO_HARDWARE})
     @interface BiometricError {}
 
+    /**
+     * Types of authenticators, defined at a level of granularity supported by
+     * {@link BiometricManager} and {@link BiometricPrompt}.
+     *
+     * <p>Types may combined via bitwise OR into a single integer representing multiple
+     * authenticators (e.g. <code>DEVICE_CREDENTIAL | BIOMETRIC_WEAK</code>).
+     */
+    public interface Authenticators {
+        /**
+         * An {@link IntDef} representing valid combinations of authenticator types.
+         * @hide
+         */
+        @IntDef(flag = true, value = {
+                BIOMETRIC_STRONG,
+                BIOMETRIC_WEAK,
+                DEVICE_CREDENTIAL,
+        })
+        @interface Types {}
+
+        /**
+         * Empty set with no authenticators specified.
+         * @hide
+         */
+        @SystemApi
+        int EMPTY_SET = 0x0;
+
+        /**
+         * Placeholder for the theoretical strongest biometric security tier.
+         * @hide
+         */
+        int BIOMETRIC_MAX_STRENGTH = 0x001;
+
+        /**
+         * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+         * requirements for <strong>Strong</strong>, as defined by the Android CDD.
+         */
+        int BIOMETRIC_STRONG = 0x00F;
+
+        /**
+         * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+         * requirements for <strong>Weak</strong>, as defined by the Android CDD.
+         *
+         * <p>Note that this is a superset of {@link #BIOMETRIC_STRONG} and is defined such that
+         * <code>BIOMETRIC_STRONG | BIOMETRIC_WEAK == BIOMETRIC_WEAK</code>.
+         */
+        int BIOMETRIC_WEAK = 0x0FF;
+
+        /**
+         * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+         * requirements for <strong>Convenience</strong>, as defined by the Android CDD. This
+         * is not a valid parameter to any of the {@link android.hardware.biometrics} APIs, since
+         * the CDD allows only {@link #BIOMETRIC_WEAK} and stronger authenticators to participate.
+         * @hide
+         */
+        @SystemApi
+        int BIOMETRIC_CONVENIENCE = 0xFFF;
+
+        /**
+         * Placeholder for the theoretical weakest biometric security tier.
+         * @hide
+         */
+        int BIOMETRIC_MIN_STRENGTH = 0x7FFF;
+
+        /**
+         * The non-biometric credential used to secure the device (i.e., PIN, pattern, or password).
+         * This should typically only be used in combination with a biometric auth type, such as
+         * {@link #BIOMETRIC_WEAK}.
+         */
+        int DEVICE_CREDENTIAL = 1 << 15;
+    }
+
     private final Context mContext;
     private final IAuthService mService;
     private final boolean mHasHardware;
@@ -94,27 +166,64 @@
     }
 
     /**
-     * Determine if biometrics can be used. In other words, determine if {@link BiometricPrompt}
-     * can be expected to be shown (hardware available, templates enrolled, user-enabled).
+     * Determine if biometrics can be used. In other words, determine if
+     * {@link BiometricPrompt} can be expected to be shown (hardware available, templates enrolled,
+     * user-enabled). This is the equivalent of {@link #canAuthenticate(int)} with
+     * {@link Authenticators#BIOMETRIC_WEAK}
      *
-     * @return Returns {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any
-     *     enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are currently
-     *     supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if a biometric can currently be
-     *     used (enrolled and available).
+     * @return {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any strong
+     *     biometrics enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are currently
+     *     supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if a strong biometric can currently
+     *     be used (enrolled and available).
+     *
+     * @deprecated See {@link #canAuthenticate(int)}.
      */
+    @Deprecated
     @RequiresPermission(USE_BIOMETRIC)
     public @BiometricError int canAuthenticate() {
-        return canAuthenticate(mContext.getUserId());
+        return canAuthenticate(Authenticators.BIOMETRIC_WEAK);
+    }
+
+    /**
+     * Determine if any of the provided authenticators can be used. In other words, determine if
+     * {@link BiometricPrompt} can be expected to be shown (hardware available, templates enrolled,
+     * user-enabled).
+     *
+     * For biometric authenticators, determine if the device can currently authenticate with at
+     * least the requested strength. For example, invoking this API with
+     * {@link Authenticators#BIOMETRIC_WEAK} on a device that currently only has
+     * {@link Authenticators#BIOMETRIC_STRONG} enrolled will return {@link #BIOMETRIC_SUCCESS}.
+     *
+     * Invoking this API with {@link Authenticators#DEVICE_CREDENTIAL} can be used to determine
+     * if the user has a PIN/Pattern/Password set up.
+     *
+     * @param authenticators bit field consisting of constants defined in {@link Authenticators}.
+     *                       If multiple authenticators are queried, a logical OR will be applied.
+     *                       For example, if {@link Authenticators#DEVICE_CREDENTIAL} |
+     *                       {@link Authenticators#BIOMETRIC_STRONG} is queried and only
+     *                       {@link Authenticators#DEVICE_CREDENTIAL} is set up, this API will
+     *                       return {@link #BIOMETRIC_SUCCESS}
+     *
+     * @return {@link #BIOMETRIC_ERROR_NONE_ENROLLED} if the user does not have any of the
+     *     requested authenticators enrolled, or {@link #BIOMETRIC_ERROR_HW_UNAVAILABLE} if none are
+     *     currently supported/enabled. Returns {@link #BIOMETRIC_SUCCESS} if one of the requested
+     *     authenticators can currently be used (enrolled and available).
+     */
+    @RequiresPermission(USE_BIOMETRIC)
+    public @BiometricError int canAuthenticate(@Authenticators.Types int authenticators) {
+        return canAuthenticate(mContext.getUserId(), authenticators);
     }
 
     /**
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public @BiometricError int canAuthenticate(int userId) {
+    public @BiometricError int canAuthenticate(int userId,
+            @Authenticators.Types int authenticators) {
         if (mService != null) {
             try {
-                return mService.canAuthenticate(mContext.getOpPackageName(), userId);
+                final String opPackageName = mContext.getOpPackageName();
+                return mService.canAuthenticate(opPackageName, userId, authenticators);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 9c51b52..6f9c9e6 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
@@ -149,7 +150,7 @@
 
     /**
      * A builder that collects arguments to be shown on the system-provided biometric dialog.
-     **/
+     */
     public static class Builder {
         private final Bundle mBundle;
         private ButtonInfo mPositiveButtonInfo;
@@ -157,8 +158,8 @@
         private Context mContext;
 
         /**
-         * Creates a builder for a biometric dialog.
-         * @param context
+         * Creates a builder for a {@link BiometricPrompt} dialog.
+         * @param context The {@link Context} that will be used to build the prompt.
          */
         public Builder(Context context) {
             mBundle = new Bundle();
@@ -166,58 +167,67 @@
         }
 
         /**
-         * Required: Set the title to display.
-         * @param title
-         * @return
+         * Required: Sets the title that will be shown on the prompt.
+         * @param title The title to display.
+         * @return This builder.
          */
-        @NonNull public Builder setTitle(@NonNull CharSequence title) {
+        @NonNull
+        public Builder setTitle(@NonNull CharSequence title) {
             mBundle.putCharSequence(KEY_TITLE, title);
             return this;
         }
 
         /**
-         * For internal use currently. Only takes effect if title is null/empty. Shows a default
-         * modality-specific title.
+         * Shows a default, modality-specific title for the prompt if the title would otherwise be
+         * null or empty. Currently for internal use only.
+         * @return This builder.
          * @hide
          */
         @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-        @NonNull public Builder setUseDefaultTitle() {
+        @NonNull
+        public Builder setUseDefaultTitle() {
             mBundle.putBoolean(KEY_USE_DEFAULT_TITLE, true);
             return this;
         }
 
         /**
-         * Optional: Set the subtitle to display.
-         * @param subtitle
-         * @return
+         * Optional: Sets a subtitle that will be shown on the prompt.
+         * @param subtitle The subtitle to display.
+         * @return This builder.
          */
-        @NonNull public Builder setSubtitle(@NonNull CharSequence subtitle) {
+        @NonNull
+        public Builder setSubtitle(@NonNull CharSequence subtitle) {
             mBundle.putCharSequence(KEY_SUBTITLE, subtitle);
             return this;
         }
 
         /**
-         * Optional: Set the description to display.
-         * @param description
-         * @return
+         * Optional: Sets a description that will be shown on the prompt.
+         * @param description The description to display.
+         * @return This builder.
          */
-        @NonNull public Builder setDescription(@NonNull CharSequence description) {
+        @NonNull
+        public Builder setDescription(@NonNull CharSequence description) {
             mBundle.putCharSequence(KEY_DESCRIPTION, description);
             return this;
         }
 
         /**
-         * Required: Set the text for the negative button. This would typically be used as a
-         * "Cancel" button, but may be also used to show an alternative method for authentication,
-         * such as screen that asks for a backup password.
+         * Required: Sets the text, executor, and click listener for the negative button on the
+         * prompt. This is typically a cancel button, but may be also used to show an alternative
+         * method for authentication, such as a screen that asks for a backup password.
          *
-         * Note that this should not be set if {@link #setDeviceCredentialAllowed(boolean)}(boolean)
-         * is set to true.
+         * <p>Note that this setting is not required, and in fact is explicitly disallowed, if
+         * device credential authentication is enabled via {@link #setAllowedAuthenticators(int)} or
+         * {@link #setDeviceCredentialAllowed(boolean)}.
          *
-         * @param text
-         * @return
+         * @param text Text to be shown on the negative button for the prompt.
+         * @param executor Executor that will be used to run the on click callback.
+         * @param listener Listener containing a callback to be run when the button is pressed.
+         * @return This builder.
          */
-        @NonNull public Builder setNegativeButton(@NonNull CharSequence text,
+        @NonNull
+        public Builder setNegativeButton(@NonNull CharSequence text,
                 @NonNull @CallbackExecutor Executor executor,
                 @NonNull DialogInterface.OnClickListener listener) {
             if (TextUtils.isEmpty(text)) {
@@ -235,70 +245,112 @@
         }
 
         /**
-         * Optional: A hint to the system to require user confirmation after a biometric has been
-         * authenticated. For example, implicit modalities like Face and Iris authentication are
-         * passive, meaning they don't require an explicit user action to complete. When set to
-         * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt
-         * will require confirmation by default.
+         * Optional: Sets a hint to the system for whether to require user confirmation after
+         * authentication. For example, implicit modalities like face and iris are passive, meaning
+         * they don't require an explicit user action to complete authentication. If set to true,
+         * these modalities should require the user to take some action (e.g. press a button)
+         * before {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is
+         * called. Defaults to true.
          *
-         * A typical use case for not requiring confirmation would be for low-risk transactions,
+         * <p>A typical use case for not requiring confirmation would be for low-risk transactions,
          * such as re-authenticating a recently authenticated application. A typical use case for
          * requiring confirmation would be for authorizing a purchase.
          *
-         * Note that this is a hint to the system. The system may choose to ignore the flag. For
-         * example, if the user disables implicit authentication in Settings, or if it does not
-         * apply to a modality (e.g. Fingerprint). When ignored, the system will default to
-         * requiring confirmation.
+         * <p>Note that this just passes a hint to the system, which the system may then ignore. For
+         * example, a value of false may be ignored if the user has disabled implicit authentication
+         * in Settings, or if it does not apply to a particular modality (e.g. fingerprint).
          *
-         * @param requireConfirmation
+         * @param requireConfirmation true if explicit user confirmation should be required, or
+         *                            false otherwise.
+         * @return This builder.
          */
-        @NonNull public Builder setConfirmationRequired(boolean requireConfirmation) {
+        @NonNull
+        public Builder setConfirmationRequired(boolean requireConfirmation) {
             mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
             return this;
         }
 
         /**
-         * The user will first be prompted to authenticate with biometrics, but also given the
-         * option to authenticate with their device PIN, pattern, or password. Developers should
-         * first check {@link KeyguardManager#isDeviceSecure()} before enabling this. If the device
-         * is not secure, {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL} will be
-         * returned in {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}}.
+         * Optional: If enabled, the user will first be prompted to authenticate with biometrics,
+         * but also given the option to authenticate with their device PIN, pattern, or password.
+         * Developers should first check {@link KeyguardManager#isDeviceSecure()} before enabling.
+         * If the device is not secure, {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL}
+         * will be given to {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
          * Defaults to false.
          *
-         * Note that {@link #setNegativeButton(CharSequence, Executor,
-         * DialogInterface.OnClickListener)} should not be set if this is set to true.
+         * <p>Note that enabling this option replaces the negative button on the prompt with one
+         * that allows the user to authenticate with their device credential, making it an error to
+         * call {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
          *
-         * @param allowed When true, the prompt will fall back to ask for the user's device
-         *               credentials (PIN, pattern, or password).
-         * @return
+         * @param allowed true if the prompt should fall back to asking for the user's device
+         *                credential (PIN/pattern/password), or false otherwise.
+         * @return This builder.
+         *
+         * @deprecated Replaced by {@link #setAllowedAuthenticators(int)}.
          */
-        @NonNull public Builder setDeviceCredentialAllowed(boolean allowed) {
+        @Deprecated
+        @NonNull
+        public Builder setDeviceCredentialAllowed(boolean allowed) {
             mBundle.putBoolean(KEY_ALLOW_DEVICE_CREDENTIAL, allowed);
             return this;
         }
 
         /**
-         * Creates a {@link BiometricPrompt}.
-         * @return a {@link BiometricPrompt}
-         * @throws IllegalArgumentException if any of the required fields are not set.
+         * Optional: Specifies the type(s) of authenticators that may be invoked by
+         * {@link BiometricPrompt} to authenticate the user. Available authenticator types are
+         * defined in {@link Authenticators} and can be combined via bitwise OR. Defaults to:
+         * <ul>
+         *     <li>{@link Authenticators#BIOMETRIC_WEAK} for non-crypto authentication, or</li>
+         *     <li>{@link Authenticators#BIOMETRIC_STRONG} for crypto-based authentication.</li>
+         * </ul>
+         *
+         * <p>If this method is used and no authenticator of any of the specified types is available
+         * at the time <code>BiometricPrompt#authenticate(...)</code> is called, authentication will
+         * be canceled and {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}
+         * will be invoked with an appropriate error code.
+         *
+         * <p>This method should be preferred over {@link #setDeviceCredentialAllowed(boolean)} and
+         * overrides the latter if both are used. Using this method to enable device credential
+         * authentication (with {@link Authenticators#DEVICE_CREDENTIAL}) will replace the negative
+         * button on the prompt, making it an error to also call
+         * {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
+         *
+         * @param authenticators A bit field representing all valid authenticator types that may be
+         *                       invoked by the prompt.
+         * @return This builder.
          */
-        @NonNull public BiometricPrompt build() {
+        @NonNull
+        public Builder setAllowedAuthenticators(@Authenticators.Types int authenticators) {
+            mBundle.putInt(KEY_AUTHENTICATORS_ALLOWED, authenticators);
+            return this;
+        }
+
+        /**
+         * Creates a {@link BiometricPrompt}.
+         *
+         * @return An instance of {@link BiometricPrompt}.
+         *
+         * @throws IllegalArgumentException If any required fields are unset, or if given any
+         * invalid combination of field values.
+         */
+        @NonNull
+        public BiometricPrompt build() {
             final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
             final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
-            final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE);
-            final boolean allowCredential = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
-            final Object authenticatorsAllowed = mBundle.get(KEY_AUTHENTICATORS_ALLOWED);
+            final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE, false);
+            final boolean deviceCredentialAllowed = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
+            final @Authenticators.Types int authenticators =
+                    mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED, 0);
+            final boolean willShowDeviceCredentialButton = deviceCredentialAllowed
+                    || (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
 
             if (TextUtils.isEmpty(title) && !useDefaultTitle) {
                 throw new IllegalArgumentException("Title must be set and non-empty");
-            } else if (TextUtils.isEmpty(negative) && !allowCredential) {
+            } else if (TextUtils.isEmpty(negative) && !willShowDeviceCredentialButton) {
                 throw new IllegalArgumentException("Negative text must be set and non-empty");
-            } else if (!TextUtils.isEmpty(negative) && allowCredential) {
+            } else if (!TextUtils.isEmpty(negative) && willShowDeviceCredentialButton) {
                 throw new IllegalArgumentException("Can't have both negative button behavior"
                         + " and device credential enabled");
-            } else if (authenticatorsAllowed != null && allowCredential) {
-                throw new IllegalArgumentException("setAuthenticatorsAllowed and"
-                        + " setDeviceCredentialAllowed should not be used simultaneously");
             }
             return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo);
         }
@@ -394,6 +446,75 @@
     }
 
     /**
+     * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}.
+     * @return The title of the prompt, which is guaranteed to be non-null.
+     */
+    @NonNull
+    public CharSequence getTitle() {
+        return mBundle.getCharSequence(KEY_TITLE, "");
+    }
+
+    /**
+     * Whether to use a default modality-specific title. For internal use only.
+     * @return See {@link Builder#setUseDefaultTitle()}.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public boolean shouldUseDefaultTitle() {
+        return mBundle.getBoolean(KEY_USE_DEFAULT_TITLE, false);
+    }
+
+    /**
+     * Gets the subtitle for the prompt, as set by {@link Builder#setSubtitle(CharSequence)}.
+     * @return The subtitle for the prompt, or null if the prompt has no subtitle.
+     */
+    @Nullable
+    public CharSequence getSubtitle() {
+        return mBundle.getCharSequence(KEY_SUBTITLE);
+    }
+
+    /**
+     * Gets the description for the prompt, as set by {@link Builder#setDescription(CharSequence)}.
+     * @return The description for the prompt, or null if the prompt has no description.
+     */
+    @Nullable
+    public CharSequence getDescription() {
+        return mBundle.getCharSequence(KEY_DESCRIPTION);
+    }
+
+    /**
+     * Gets the negative button text for the prompt, as set by
+     * {@link Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}.
+     * @return The negative button text for the prompt, or null if no negative button text was set.
+     */
+    @Nullable
+    public CharSequence getNegativeButtonText() {
+        return mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
+    }
+
+    /**
+     * Determines if explicit user confirmation is required by the prompt, as set by
+     * {@link Builder#setConfirmationRequired(boolean)}.
+     *
+     * @return true if explicit user confirmation is required, or false otherwise.
+     */
+    public boolean isConfirmationRequired() {
+        return mBundle.getBoolean(KEY_REQUIRE_CONFIRMATION, true);
+    }
+
+    /**
+     * Gets the type(s) of authenticators that may be invoked by the prompt to authenticate the
+     * user, as set by {@link Builder#setAllowedAuthenticators(int)}.
+     *
+     * @return A bit field representing the type(s) of authenticators that may be invoked by the
+     * prompt (as defined by {@link Authenticators}), or 0 if this field was not set.
+     */
+    @Nullable
+    public int getAllowedAuthenticators() {
+        return mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED, 0);
+    }
+
+    /**
      * A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework
      * supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
      */
@@ -509,10 +630,12 @@
 
     /**
      * Authenticates for the given user.
+     *
      * @param cancel An object that can be used to cancel authentication
      * @param executor An executor to handle callback events
      * @param callback An object to receive authentication events
      * @param userId The user to authenticate
+     *
      * @hide
      */
     @RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -542,23 +665,33 @@
      * authentication errors through {@link AuthenticationCallback}, and button events through the
      * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor,
      * DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricPrompt} object,
-     * and calling {@link BiometricPrompt#authenticate( CancellationSignal, Executor,
+     * and calling {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
      * AuthenticationCallback)} while an existing authentication attempt is occurring will stop the
      * previous client and start a new authentication. The interrupted client will receive a
      * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int,
      * CharSequence)}.
      *
-     * Note: Applications generally should not cancel and start authentication in quick succession.
-     * For example, to properly handle authentication across configuration changes, it's recommended
-     * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the
-     * application will not need to cancel/restart authentication during the configuration change.
+     * <p>Note: Applications generally should not cancel and start authentication in quick
+     * succession. For example, to properly handle authentication across configuration changes, it's
+     * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so,
+     * the application will not need to cancel/restart authentication during the configuration
+     * change.
      *
-     * @throws IllegalArgumentException If any of the arguments are null
+     * <p>Per the Android CDD, only biometric authenticators that meet or exceed the requirements
+     * for <strong>Strong</strong> are permitted to integrate with Keystore to perform related
+     * cryptographic operations. Therefore, it is an error to call this method after explicitly
+     * calling {@link Builder#setAllowedAuthenticators(int)} with any value other than
+     * {@link Authenticators#BIOMETRIC_STRONG}.
      *
-     * @param crypto Object associated with the call
-     * @param cancel An object that can be used to cancel authentication
-     * @param executor An executor to handle callback events
-     * @param callback An object to receive authentication events
+     * @throws IllegalArgumentException If any of the arguments are null, if
+     * {@link Builder#setDeviceCredentialAllowed(boolean)} was explicitly set to true, or if
+     * {@link Builder#setAllowedAuthenticators(int)} was explicitly called with any value other than
+     * {@link Authenticators#BIOMETRIC_STRONG}.
+     *
+     * @param crypto A cryptographic operation to be unlocked after successful authentication.
+     * @param cancel An object that can be used to cancel authentication.
+     * @param executor An executor to handle callback events.
+     * @param callback An object to receive authentication events.
      */
     @RequiresPermission(USE_BIOMETRIC)
     public void authenticate(@NonNull CryptoObject crypto,
@@ -577,9 +710,20 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
-        if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) {
+
+        @Authenticators.Types int authenticators = mBundle.getInt(
+                KEY_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_STRONG);
+
+        if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)
+                || (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0) {
             throw new IllegalArgumentException("Device credential not supported with crypto");
         }
+
+        // Disallow any non-Strong biometric authenticator types.
+        if ((authenticators & ~Authenticators.BIOMETRIC_STRONG) != 0) {
+            throw new IllegalArgumentException("Only Strong biometrics supported with crypto");
+        }
+
         authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
     }
 
@@ -598,16 +742,17 @@
      * authentication. The interrupted client will receive a cancelled notification through {@link
      * AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
      *
-     * Note: Applications generally should not cancel and start authentication in quick succession.
-     * For example, to properly handle authentication across configuration changes, it's recommended
-     * to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so, the
-     * application will not need to cancel/restart authentication during the configuration change.
+     * <p>Note: Applications generally should not cancel and start authentication in quick
+     * succession. For example, to properly handle authentication across configuration changes, it's
+     * recommended to use BiometricPrompt in a fragment with setRetainInstance(true). By doing so,
+     * the application will not need to cancel/restart authentication during the configuration
+     * change.
      *
-     * @throws IllegalArgumentException If any of the arguments are null
+     * @throws IllegalArgumentException If any of the arguments are null.
      *
-     * @param cancel An object that can be used to cancel authentication
-     * @param executor An executor to handle callback events
-     * @param callback An object to receive authentication events
+     * @param cancel An object that can be used to cancel authentication.
+     * @param executor An executor to handle callback events.
+     * @param callback An object to receive authentication events.
      */
     @RequiresPermission(USE_BIOMETRIC)
     public void authenticate(@NonNull CancellationSignal cancel,
@@ -653,8 +798,22 @@
             mAuthenticationCallback = callback;
             final long sessionId = crypto != null ? crypto.getOpId() : 0;
             if (BiometricManager.hasBiometrics(mContext)) {
+                final Bundle bundle;
+                if (crypto != null) {
+                    // Allowed authenticators should default to BIOMETRIC_STRONG for crypto auth.
+                    // Note that we use a new bundle here so as to not overwrite the application's
+                    // preference, since it is possible that the same prompt configuration be used
+                    // without a crypto object later.
+                    bundle = new Bundle(mBundle);
+                    bundle.putInt(KEY_AUTHENTICATORS_ALLOWED,
+                            mBundle.getInt(KEY_AUTHENTICATORS_ALLOWED,
+                                    Authenticators.BIOMETRIC_STRONG));
+                } else {
+                    bundle = mBundle;
+                }
+
                 mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
-                        mContext.getOpPackageName(), mBundle);
+                        mContext.getOpPackageName(), bundle);
             } else {
                 mExecutor.execute(() -> {
                     callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT,
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 516a25d..d482198 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -35,7 +35,7 @@
 
     // TODO(b/141025588): Make userId the first arg to be consistent with hasEnrolledBiometrics.
     // Checks if biometrics can be used.
-    int canAuthenticate(String opPackageName, int userId);
+    int canAuthenticate(String opPackageName, int userId, int authenticators);
 
     // Checks if any biometrics are enrolled.
     boolean hasEnrolledBiometrics(int userId, String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index ca02421..8a6be18 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -35,7 +35,7 @@
     void cancelAuthentication(IBinder token, String opPackageName);
 
     // Checks if biometrics can be used.
-    int canAuthenticate(String opPackageName, int userId);
+    int canAuthenticate(String opPackageName, int userId, int authenticators);
 
     // Checks if any biometrics are enrolled.
     boolean hasEnrolledBiometrics(int userId, String opPackageName);
@@ -43,7 +43,8 @@
     // Registers an authenticator (e.g. face, fingerprint, iris).
     // Id must be unique, whereas strength and modality don't need to be.
     // TODO(b/123321528): Turn strength and modality into enums.
-    void registerAuthenticator(int id, int strength, int modality, IBiometricAuthenticator authenticator);
+    void registerAuthenticator(int id, int modality, int strength,
+            IBiometricAuthenticator authenticator);
 
     // Register callback for when keyguard biometric eligibility changes.
     void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1f29d1a..c84c4a7 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -935,7 +935,7 @@
     /**
      * <p>List of the maximum number of regions that can be used for metering in
      * auto-exposure (AE), auto-white balance (AWB), and auto-focus (AF);
-     * this corresponds to the the maximum number of elements in
+     * this corresponds to the maximum number of elements in
      * {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}, {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions},
      * and {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p>
      * <p><b>Range of valid values:</b><br></p>
@@ -955,7 +955,7 @@
     /**
      * <p>The maximum number of metering regions that can be used by the auto-exposure (AE)
      * routine.</p>
-     * <p>This corresponds to the the maximum allowed number of elements in
+     * <p>This corresponds to the maximum allowed number of elements in
      * {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}.</p>
      * <p><b>Range of valid values:</b><br>
      * Value will be &gt;= 0. For FULL-capability devices, this
@@ -973,7 +973,7 @@
     /**
      * <p>The maximum number of metering regions that can be used by the auto-white balance (AWB)
      * routine.</p>
-     * <p>This corresponds to the the maximum allowed number of elements in
+     * <p>This corresponds to the maximum allowed number of elements in
      * {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}.</p>
      * <p><b>Range of valid values:</b><br>
      * Value will be &gt;= 0.</p>
@@ -989,7 +989,7 @@
 
     /**
      * <p>The maximum number of metering regions that can be used by the auto-focus (AF) routine.</p>
-     * <p>This corresponds to the the maximum allowed number of elements in
+     * <p>This corresponds to the maximum allowed number of elements in
      * {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p>
      * <p><b>Range of valid values:</b><br>
      * Value will be &gt;= 0. For FULL-capability devices, this
@@ -1987,6 +1987,7 @@
      *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}</li>
      *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA SECURE_IMAGE_DATA}</li>
      *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA SYSTEM_CAMERA}</li>
+     *   <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING}</li>
      * </ul></p>
      * <p>This key is available on all devices.</p>
      *
@@ -2006,6 +2007,7 @@
      * @see #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME
      * @see #REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA
      * @see #REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA
+     * @see #REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 799c716..f2a7abd 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1004,6 +1004,51 @@
      */
     public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14;
 
+    /**
+     * <p>The camera device supports the OFFLINE_PROCESSING use case.</p>
+     * <p>With OFFLINE_PROCESSING capability, the application can switch an ongoing
+     * capture session to offline mode by calling the
+     * CameraCaptureSession#switchToOffline method and specify streams to be kept in offline
+     * mode. The camera will then stop currently active repeating requests, prepare for
+     * some requests to go into offline mode, and return an offline session object. After
+     * the switchToOffline call returns, the original capture session is in closed state as
+     * if the CameraCaptureSession#close method has been called.
+     * In the offline mode, all inflight requests will continue to be processed in the
+     * background, and the application can immediately close the camera or create a new
+     * capture session without losing those requests' output images and capture results.</p>
+     * <p>While the camera device is processing offline requests, it
+     * might not be able to support all stream configurations it can support
+     * without offline requests. When that happens, the createCaptureSession
+     * method call will fail. The following stream configurations are guaranteed to work
+     * without hitting the resource busy exception:</p>
+     * <ul>
+     * <li>One ongoing offline session: target one output surface of YUV or
+     * JPEG format, any resolution.</li>
+     * <li>The active camera capture session:<ol>
+     * <li>One preview surface (SurfaceView or SurfaceTexture) up to 1920 width</li>
+     * <li>One YUV ImageReader surface up to 1920 width</li>
+     * <li>One Jpeg ImageReader, any resolution: the camera device is
+     *    allowed to slow down JPEG output speed by 50% if there is any ongoing offline
+     *    session.</li>
+     * <li>One ImageWriter surface of private format, any resolution if the device supports
+     *    PRIVATE_REPROCESSING capability</li>
+     * </ol>
+     * </li>
+     * <li>Alternatively, the active camera session above can be replaced by an legacy
+     * {@link android.hardware.Camera Camera} with the following parameter settings:<ol>
+     * <li>Preview size up to 1920 width</li>
+     * <li>Preview callback size up to 1920 width</li>
+     * <li>Video size up to 1920 width</li>
+     * <li>Picture size, any resolution: the camera device is
+     *     allowed to slow down JPEG output speed by 50% if there is any ongoing offline
+     *     session.</li>
+     * </ol>
+     * </li>
+     * </ul>
+     * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+     */
+    public static final int REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING = 15;
+
     //
     // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
     //
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index bcbc337..41435c9 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -29,6 +29,7 @@
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.ICameraOfflineSession;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.OutputConfiguration;
@@ -866,6 +867,38 @@
         }
     }
 
+    public void switchToOffline(ICameraDeviceCallbacks cbs, Surface[] offlineOutputs)
+            throws CameraAccessException {
+        if ((offlineOutputs == null) || (offlineOutputs.length == 0)) {
+            throw new IllegalArgumentException("Invalid offline outputs!");
+        }
+        if (cbs == null) {
+            throw new IllegalArgumentException("Invalid device callbacks!");
+        }
+
+        ICameraOfflineSession offlineSession = null;
+        synchronized(mInterfaceLock) {
+            int streamId = -1;
+            for (Surface surface : offlineOutputs) {
+                for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+                    if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
+                        streamId = mConfiguredOutputs.keyAt(i);
+                        break;
+                    }
+                }
+                if (streamId == -1) {
+                    throw new IllegalArgumentException("Offline surface is not part of this" +
+                            " session");
+                }
+            }
+
+            offlineSession = mRemoteDevice.switchToOffline(cbs,
+                    offlineOutputs);
+            // TODO: Initialize CameraOfflineSession wrapper, clear 'mConfiguredOutputs',
+            // and update request tracking
+        }
+    }
+
     public void tearDown(Surface surface) throws CameraAccessException {
         if (surface == null) throw new IllegalArgumentException("Surface is null");
 
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 3660f29..397417b 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -28,6 +28,8 @@
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.ICameraDeviceCallbacks;
+import android.hardware.camera2.ICameraOfflineSession;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
@@ -248,6 +250,17 @@
         }
     }
 
+    public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
+            Surface[] offlineOutputs)
+            throws CameraAccessException {
+        try {
+            return mRemoteDevice.switchToOffline(cbs, offlineOutputs);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
     public void finalizeOutputConfigurations(int streamId, OutputConfiguration deferredConfig)
             throws CameraAccessException {
         try {
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 5d1435a..6ab0c29 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -24,6 +24,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.ICameraOfflineSession;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
 import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
@@ -789,6 +790,12 @@
     }
 
     @Override
+    public ICameraOfflineSession switchToOffline(ICameraDeviceCallbacks cbs,
+            Surface[] offlineOutputs) {
+        throw new UnsupportedOperationException("Legacy device does not support switchToOffline");
+    }
+
+    @Override
     public IBinder asBinder() {
         // This is solely intended to be used for in-process binding.
         return null;
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 6eaf54b..23f18a8 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -1163,7 +1163,7 @@
             if (orderedPreviewSizes != null) {
                 for (Size size : orderedPreviewSizes) {
                     if ((mDisplaySize.getWidth() >= size.getWidth()) &&
-                            (mDisplaySize.getWidth() >= size.getHeight())) {
+                            (mDisplaySize.getHeight() >= size.getHeight())) {
                         return size;
                     }
                 }
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
new file mode 100644
index 0000000..8231c58
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.soundtrigger;
+
+import android.hardware.soundtrigger.ModelParams;
+import android.media.AudioFormat;
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+import android.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+/** @hide */
+class ConversionUtil {
+    public static SoundTrigger.ModuleProperties aidl2apiModuleDescriptor(
+            SoundTriggerModuleDescriptor aidlDesc) {
+        SoundTriggerModuleProperties properties = aidlDesc.properties;
+        return new SoundTrigger.ModuleProperties(
+                aidlDesc.handle,
+                properties.implementor,
+                properties.description,
+                properties.uuid,
+                properties.version,
+                properties.maxSoundModels,
+                properties.maxKeyPhrases,
+                properties.maxUsers,
+                aidl2apiRecognitionModes(properties.recognitionModes),
+                properties.captureTransition,
+                properties.maxBufferMs,
+                properties.concurrentCapture,
+                properties.powerConsumptionMw,
+                properties.triggerInEvent
+        );
+    }
+
+    public static int aidl2apiRecognitionModes(int aidlModes) {
+        int result = 0;
+        if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+            result |= SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
+        }
+        if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+            result |= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+        }
+        if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+            result |= SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
+        }
+        if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+            result |= SoundTrigger.RECOGNITION_MODE_GENERIC;
+        }
+        return result;
+    }
+
+    public static int api2aidlRecognitionModes(int apiModes) {
+        int result = 0;
+        if ((apiModes & SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER) != 0) {
+            result |= RecognitionMode.VOICE_TRIGGER;
+        }
+        if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION) != 0) {
+            result |= RecognitionMode.USER_IDENTIFICATION;
+        }
+        if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION) != 0) {
+            result |= RecognitionMode.USER_AUTHENTICATION;
+        }
+        if ((apiModes & SoundTrigger.RECOGNITION_MODE_GENERIC) != 0) {
+            result |= RecognitionMode.GENERIC_TRIGGER;
+        }
+        return result;
+    }
+
+
+    public static SoundModel api2aidlGenericSoundModel(SoundTrigger.GenericSoundModel apiModel) {
+        return api2aidlSoundModel(apiModel);
+    }
+
+    public static SoundModel api2aidlSoundModel(SoundTrigger.SoundModel apiModel) {
+        SoundModel aidlModel = new SoundModel();
+        aidlModel.type = apiModel.type;
+        aidlModel.uuid = api2aidlUuid(apiModel.uuid);
+        aidlModel.vendorUuid = api2aidlUuid(apiModel.vendorUuid);
+        aidlModel.data = Arrays.copyOf(apiModel.data, apiModel.data.length);
+        return aidlModel;
+    }
+
+    public static String api2aidlUuid(UUID apiUuid) {
+        return apiUuid.toString();
+    }
+
+    public static PhraseSoundModel api2aidlPhraseSoundModel(
+            SoundTrigger.KeyphraseSoundModel apiModel) {
+        PhraseSoundModel aidlModel = new PhraseSoundModel();
+        aidlModel.common = api2aidlSoundModel(apiModel);
+        aidlModel.phrases = new Phrase[apiModel.keyphrases.length];
+        for (int i = 0; i < apiModel.keyphrases.length; ++i) {
+            aidlModel.phrases[i] = api2aidlPhrase(apiModel.keyphrases[i]);
+        }
+        return aidlModel;
+    }
+
+    public static Phrase api2aidlPhrase(SoundTrigger.Keyphrase apiPhrase) {
+        Phrase aidlPhrase = new Phrase();
+        aidlPhrase.id = apiPhrase.id;
+        aidlPhrase.recognitionModes = api2aidlRecognitionModes(apiPhrase.recognitionModes);
+        aidlPhrase.users = Arrays.copyOf(apiPhrase.users, apiPhrase.users.length);
+        aidlPhrase.locale = apiPhrase.locale;
+        aidlPhrase.text = apiPhrase.text;
+        return aidlPhrase;
+    }
+
+    public static RecognitionConfig api2aidlRecognitionConfig(
+            SoundTrigger.RecognitionConfig apiConfig) {
+        RecognitionConfig aidlConfig = new RecognitionConfig();
+        aidlConfig.captureRequested = apiConfig.captureRequested;
+        // apiConfig.allowMultipleTriggers is ignored by the lower layers.
+        aidlConfig.phraseRecognitionExtras =
+                new PhraseRecognitionExtra[apiConfig.keyphrases.length];
+        for (int i = 0; i < apiConfig.keyphrases.length; ++i) {
+            aidlConfig.phraseRecognitionExtras[i] = api2aidlPhraseRecognitionExtra(
+                    apiConfig.keyphrases[i]);
+        }
+        aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+        return aidlConfig;
+    }
+
+    public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra(
+            SoundTrigger.KeyphraseRecognitionExtra apiExtra) {
+        PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+        aidlExtra.id = apiExtra.id;
+        aidlExtra.recognitionModes = api2aidlRecognitionModes(apiExtra.recognitionModes);
+        aidlExtra.confidenceLevel = apiExtra.coarseConfidenceLevel;
+        aidlExtra.levels = new ConfidenceLevel[apiExtra.confidenceLevels.length];
+        for (int i = 0; i < apiExtra.confidenceLevels.length; ++i) {
+            aidlExtra.levels[i] = api2aidlConfidenceLevel(apiExtra.confidenceLevels[i]);
+        }
+        return aidlExtra;
+    }
+
+    public static SoundTrigger.KeyphraseRecognitionExtra aidl2apiPhraseRecognitionExtra(
+            PhraseRecognitionExtra aidlExtra) {
+        SoundTrigger.ConfidenceLevel[] apiLevels =
+                new SoundTrigger.ConfidenceLevel[aidlExtra.levels.length];
+        for (int i = 0; i < aidlExtra.levels.length; ++i) {
+            apiLevels[i] = aidl2apiConfidenceLevel(aidlExtra.levels[i]);
+        }
+        return new SoundTrigger.KeyphraseRecognitionExtra(aidlExtra.id,
+                aidl2apiRecognitionModes(aidlExtra.recognitionModes),
+                aidlExtra.confidenceLevel, apiLevels);
+    }
+
+    public static ConfidenceLevel api2aidlConfidenceLevel(
+            SoundTrigger.ConfidenceLevel apiLevel) {
+        ConfidenceLevel aidlLevel = new ConfidenceLevel();
+        aidlLevel.levelPercent = apiLevel.confidenceLevel;
+        aidlLevel.userId = apiLevel.userId;
+        return aidlLevel;
+    }
+
+    public static SoundTrigger.ConfidenceLevel aidl2apiConfidenceLevel(
+            ConfidenceLevel apiLevel) {
+        return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent);
+    }
+
+    public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
+            int modelHandle, RecognitionEvent aidlEvent) {
+        return new SoundTrigger.GenericRecognitionEvent(
+                aidlEvent.status,
+                modelHandle, aidlEvent.captureAvailable, aidlEvent.captureSession,
+                aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData,
+                aidl2apiAudioFormat(aidlEvent.audioConfig), aidlEvent.data);
+    }
+
+    public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
+            int modelHandle,
+            PhraseRecognitionEvent aidlEvent) {
+        SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
+                new SoundTrigger.KeyphraseRecognitionExtra[aidlEvent.phraseExtras.length];
+        for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) {
+            apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]);
+        }
+        return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
+                aidlEvent.common.captureAvailable,
+                aidlEvent.common.captureSession, aidlEvent.common.captureDelayMs,
+                aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData,
+                aidl2apiAudioFormat(aidlEvent.common.audioConfig), aidlEvent.common.data,
+                apiExtras);
+    }
+
+    public static AudioFormat aidl2apiAudioFormat(AudioConfig audioConfig) {
+        AudioFormat.Builder apiBuilder = new AudioFormat.Builder();
+        apiBuilder.setSampleRate(audioConfig.sampleRateHz);
+        apiBuilder.setChannelMask(aidl2apiChannelInMask(audioConfig.channelMask));
+        apiBuilder.setEncoding(aidl2apiEncoding(audioConfig.format));
+        return apiBuilder.build();
+    }
+
+    public static int aidl2apiEncoding(int aidlFormat) {
+        switch (aidlFormat) {
+            case android.media.audio.common.AudioFormat.PCM
+                    | android.media.audio.common.AudioFormat.PCM_SUB_16_BIT:
+                return AudioFormat.ENCODING_PCM_16BIT;
+
+            case android.media.audio.common.AudioFormat.PCM
+                    | android.media.audio.common.AudioFormat.PCM_SUB_8_BIT:
+                return AudioFormat.ENCODING_PCM_8BIT;
+
+            case android.media.audio.common.AudioFormat.PCM
+                    | android.media.audio.common.AudioFormat.PCM_SUB_FLOAT:
+            case android.media.audio.common.AudioFormat.PCM
+                    | android.media.audio.common.AudioFormat.PCM_SUB_8_24_BIT:
+            case android.media.audio.common.AudioFormat.PCM
+                    | android.media.audio.common.AudioFormat.PCM_SUB_24_BIT_PACKED:
+            case android.media.audio.common.AudioFormat.PCM
+                    | android.media.audio.common.AudioFormat.PCM_SUB_32_BIT:
+                return AudioFormat.ENCODING_PCM_FLOAT;
+
+            case android.media.audio.common.AudioFormat.AC3:
+                return AudioFormat.ENCODING_AC3;
+
+            case android.media.audio.common.AudioFormat.E_AC3:
+                return AudioFormat.ENCODING_E_AC3;
+
+            case android.media.audio.common.AudioFormat.DTS:
+                return AudioFormat.ENCODING_DTS;
+
+            case android.media.audio.common.AudioFormat.DTS_HD:
+                return AudioFormat.ENCODING_DTS_HD;
+
+            case android.media.audio.common.AudioFormat.MP3:
+                return AudioFormat.ENCODING_MP3;
+
+            case android.media.audio.common.AudioFormat.AAC
+                    | android.media.audio.common.AudioFormat.AAC_SUB_LC:
+                return AudioFormat.ENCODING_AAC_LC;
+
+            case android.media.audio.common.AudioFormat.AAC
+                    | android.media.audio.common.AudioFormat.AAC_SUB_HE_V1:
+                return AudioFormat.ENCODING_AAC_HE_V1;
+
+            case android.media.audio.common.AudioFormat.AAC
+                    | android.media.audio.common.AudioFormat.AAC_SUB_HE_V2:
+                return AudioFormat.ENCODING_AAC_HE_V2;
+
+            case android.media.audio.common.AudioFormat.IEC61937:
+                return AudioFormat.ENCODING_IEC61937;
+
+            case android.media.audio.common.AudioFormat.DOLBY_TRUEHD:
+                return AudioFormat.ENCODING_DOLBY_TRUEHD;
+
+            case android.media.audio.common.AudioFormat.AAC
+                    | android.media.audio.common.AudioFormat.AAC_SUB_ELD:
+                return AudioFormat.ENCODING_AAC_ELD;
+
+            case android.media.audio.common.AudioFormat.AAC
+                    | android.media.audio.common.AudioFormat.AAC_SUB_XHE:
+                return AudioFormat.ENCODING_AAC_XHE;
+
+            case android.media.audio.common.AudioFormat.AC4:
+                return AudioFormat.ENCODING_AC4;
+
+            case android.media.audio.common.AudioFormat.E_AC3
+                    | android.media.audio.common.AudioFormat.E_AC3_SUB_JOC:
+                return AudioFormat.ENCODING_E_AC3_JOC;
+
+            case android.media.audio.common.AudioFormat.MAT:
+            case android.media.audio.common.AudioFormat.MAT
+                    | android.media.audio.common.AudioFormat.MAT_SUB_1_0:
+            case android.media.audio.common.AudioFormat.MAT
+                    | android.media.audio.common.AudioFormat.MAT_SUB_2_0:
+            case android.media.audio.common.AudioFormat.MAT
+                    | android.media.audio.common.AudioFormat.MAT_SUB_2_1:
+                return AudioFormat.ENCODING_DOLBY_MAT;
+
+            case android.media.audio.common.AudioFormat.DEFAULT:
+                return AudioFormat.ENCODING_DEFAULT;
+
+            default:
+                return AudioFormat.ENCODING_INVALID;
+        }
+    }
+
+    public static int api2aidlModelParameter(int apiParam) {
+        switch (apiParam) {
+            case ModelParams.THRESHOLD_FACTOR:
+                return android.media.soundtrigger_middleware.ModelParameter.THRESHOLD_FACTOR;
+            default:
+                return android.media.soundtrigger_middleware.ModelParameter.INVALID;
+        }
+    }
+
+    public static int aidl2apiChannelInMask(int aidlMask) {
+        // We're assuming AudioFormat.CHANNEL_IN_* constants are kept in sync with
+        // android.media.audio.common.AudioChannelMask.
+        return aidlMask;
+    }
+
+    public static SoundTrigger.ModelParamRange aidl2apiModelParameterRange(
+            @Nullable ModelParameterRange aidlRange) {
+        if (aidlRange == null) {
+            return null;
+        }
+        return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
+    }
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 86f3eec..5484df4 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -22,18 +22,29 @@
 import static android.system.OsConstants.EPERM;
 import static android.system.OsConstants.EPIPE;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
+import android.content.Context;
 import android.media.AudioFormat;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.UUID;
 
 /**
@@ -44,6 +55,7 @@
  */
 @SystemApi
 public class SoundTrigger {
+    private static final String TAG = "SoundTrigger";
 
     private SoundTrigger() {
     }
@@ -119,15 +131,15 @@
          * recognition callback event */
         public final boolean returnsTriggerInEvent;
 
-        ModuleProperties(int id, String implementor, String description,
-                String uuid, int version, int maxSoundModels, int maxKeyphrases,
+        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) {
             this.id = id;
-            this.implementor = implementor;
-            this.description = description;
-            this.uuid = UUID.fromString(uuid);
+            this.implementor = requireNonNull(implementor);
+            this.description = requireNonNull(description);
+            this.uuid = UUID.fromString(requireNonNull(uuid));
             this.version = version;
             this.maxSoundModels = maxSoundModels;
             this.maxKeyphrases = maxKeyphrases;
@@ -231,6 +243,7 @@
 
         /** Unique sound model identifier */
         @UnsupportedAppUsage
+        @NonNull
         public final UUID uuid;
 
         /** Sound model type (e.g. TYPE_KEYPHRASE); */
@@ -238,17 +251,20 @@
 
         /** Unique sound model vendor identifier */
         @UnsupportedAppUsage
+        @NonNull
         public final UUID vendorUuid;
 
         /** Opaque data. For use by vendor implementation and enrollment application */
         @UnsupportedAppUsage
+        @NonNull
         public final byte[] data;
 
-        public SoundModel(UUID uuid, UUID vendorUuid, int type, byte[] data) {
-            this.uuid = uuid;
-            this.vendorUuid = vendorUuid;
+        public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type,
+                @Nullable byte[] data) {
+            this.uuid = requireNonNull(uuid);
+            this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0);
             this.type = type;
-            this.data = data;
+            this.data = data != null ? data : new byte[0];
         }
 
         @Override
@@ -271,8 +287,6 @@
             if (!(obj instanceof SoundModel))
                 return false;
             SoundModel other = (SoundModel) obj;
-            if (!Arrays.equals(data, other.data))
-                return false;
             if (type != other.type)
                 return false;
             if (uuid == null) {
@@ -285,6 +299,8 @@
                     return false;
             } else if (!vendorUuid.equals(other.vendorUuid))
                 return false;
+            if (!Arrays.equals(data, other.data))
+                return false;
             return true;
         }
     }
@@ -306,24 +322,28 @@
 
         /** Locale of the keyphrase. JAVA Locale string e.g en_US */
         @UnsupportedAppUsage
+        @NonNull
         public final String locale;
 
         /** Key phrase text */
         @UnsupportedAppUsage
+        @NonNull
         public final String text;
 
         /** Users this key phrase has been trained for. countains sound trigger specific user IDs
          * derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. */
         @UnsupportedAppUsage
+        @NonNull
         public final int[] users;
 
         @UnsupportedAppUsage
-        public Keyphrase(int id, int recognitionModes, String locale, String text, int[] users) {
+        public Keyphrase(int id, int recognitionModes, @NonNull String locale, @NonNull String text,
+                @Nullable int[] users) {
             this.id = id;
             this.recognitionModes = recognitionModes;
-            this.locale = locale;
-            this.text = text;
-            this.users = users;
+            this.locale = requireNonNull(locale);
+            this.text = requireNonNull(text);
+            this.users = users != null ? users : new int[0];
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<Keyphrase> CREATOR
@@ -427,13 +447,15 @@
     public static class KeyphraseSoundModel extends SoundModel implements Parcelable {
         /** Key phrases in this sound model */
         @UnsupportedAppUsage
+        @NonNull
         public final Keyphrase[] keyphrases; // keyword phrases in model
 
         @UnsupportedAppUsage
         public KeyphraseSoundModel(
-                UUID uuid, UUID vendorUuid, byte[] data, Keyphrase[] keyphrases) {
+                @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data,
+                @Nullable Keyphrase[] keyphrases) {
             super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
-            this.keyphrases = keyphrases;
+            this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0];
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR
@@ -528,7 +550,8 @@
         };
 
         @UnsupportedAppUsage
-        public GenericSoundModel(UUID uuid, UUID vendorUuid, byte[] data) {
+        public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+                @Nullable byte[] data) {
             super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
         }
 
@@ -648,6 +671,12 @@
      * @hide
      */
     public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4;
+    /**
+     * Generic (non-speech) recognition.
+     *
+     * @hide
+     */
+    public static final int RECOGNITION_MODE_GENERIC = 0x8;
 
     /**
      *  Status codes for {@link RecognitionEvent}
@@ -739,6 +768,7 @@
          *
          * @hide
          */
+        @NonNull
         public final AudioFormat captureFormat;
         /**
          * Opaque data for use by system applications who know about voice engine internals,
@@ -747,13 +777,14 @@
          * @hide
          */
         @UnsupportedAppUsage
+        @NonNull
         public final byte[] data;
 
         /** @hide */
         @UnsupportedAppUsage
         public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
                 int captureSession, int captureDelayMs, int capturePreambleMs,
-                boolean triggerInData, AudioFormat captureFormat, byte[] data) {
+                boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data) {
             this.status = status;
             this.soundModelHandle = soundModelHandle;
             this.captureAvailable = captureAvailable;
@@ -761,8 +792,8 @@
             this.captureDelayMs = captureDelayMs;
             this.capturePreambleMs = capturePreambleMs;
             this.triggerInData = triggerInData;
-            this.captureFormat = captureFormat;
-            this.data = data;
+            this.captureFormat = requireNonNull(captureFormat);
+            this.data = data != null ? data : new byte[0];
         }
 
         /**
@@ -965,19 +996,21 @@
         /** List of all keyphrases in the sound model for which recognition should be performed with
          * options for each keyphrase. */
         @UnsupportedAppUsage
+        @NonNull
         public final KeyphraseRecognitionExtra keyphrases[];
         /** Opaque data for use by system applications who know about voice engine internals,
          * typically during enrollment. */
         @UnsupportedAppUsage
+        @NonNull
         public final byte[] data;
 
         @UnsupportedAppUsage
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                KeyphraseRecognitionExtra[] keyphrases, byte[] data) {
+                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
             this.captureRequested = captureRequested;
             this.allowMultipleTriggers = allowMultipleTriggers;
-            this.keyphrases = keyphrases;
-            this.data = data;
+            this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
+            this.data = data != null ? data : new byte[0];
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1126,15 +1159,17 @@
         /** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
          * be recognized (RecognitionConfig) */
         @UnsupportedAppUsage
+        @NonNull
         public final ConfidenceLevel[] confidenceLevels;
 
         @UnsupportedAppUsage
         public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
-                ConfidenceLevel[] confidenceLevels) {
+                @Nullable ConfidenceLevel[] confidenceLevels) {
             this.id = id;
             this.recognitionModes = recognitionModes;
             this.coarseConfidenceLevel = coarseConfidenceLevel;
-            this.confidenceLevels = confidenceLevels;
+            this.confidenceLevels =
+                    confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0];
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
@@ -1217,16 +1252,18 @@
     public static class KeyphraseRecognitionEvent extends RecognitionEvent implements Parcelable {
         /** Indicates if the key phrase is present in the buffered audio available for capture */
         @UnsupportedAppUsage
+        @NonNull
         public final KeyphraseRecognitionExtra[] keyphraseExtras;
 
         @UnsupportedAppUsage
         public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
                int captureSession, int captureDelayMs, int capturePreambleMs,
-               boolean triggerInData, AudioFormat captureFormat, byte[] data,
-               KeyphraseRecognitionExtra[] keyphraseExtras) {
+               boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+               @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) {
             super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
                   capturePreambleMs, triggerInData, captureFormat, data);
-            this.keyphraseExtras = keyphraseExtras;
+            this.keyphraseExtras =
+                    keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
@@ -1343,8 +1380,8 @@
         @UnsupportedAppUsage
         public GenericRecognitionEvent(int status, int soundModelHandle,
                 boolean captureAvailable, int captureSession, int captureDelayMs,
-                int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat,
-                byte[] data) {
+                int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat,
+                @Nullable byte[] data) {
             super(status, soundModelHandle, captureAvailable, captureSession,
                     captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
                     data);
@@ -1408,13 +1445,14 @@
         /** The updated sound model handle */
         public final int soundModelHandle;
         /** New sound model data */
+        @NonNull
         public final byte[] data;
 
         @UnsupportedAppUsage
-        SoundModelEvent(int status, int soundModelHandle, byte[] data) {
+        SoundModelEvent(int status, int soundModelHandle, @Nullable byte[] data) {
             this.status = status;
             this.soundModelHandle = soundModelHandle;
-            this.data = data;
+            this.data = data != null ? data : new byte[0];
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<SoundModelEvent> CREATOR
@@ -1498,8 +1536,9 @@
      * @hide
      */
     public static final int SERVICE_STATE_DISABLED = 1;
-
-    /**
+    private static Object mServiceLock = new Object();
+    private static ISoundTriggerMiddlewareService mService;
+   /**
      * @return returns current package name.
      */
     static String getCurrentOpPackageName() {
@@ -1523,25 +1562,22 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public static int listModules(ArrayList<ModuleProperties> modules) {
-        return listModules(getCurrentOpPackageName(), modules);
+    public static int listModules(@NonNull ArrayList<ModuleProperties> modules) {
+        try {
+            SoundTriggerModuleDescriptor[] descs = getService().listModules();
+            modules.clear();
+            modules.ensureCapacity(descs.length);
+            for (SoundTriggerModuleDescriptor desc : descs) {
+                modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
+            }
+            return STATUS_OK;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception caught", e);
+            return STATUS_DEAD_OBJECT;
+        }
     }
 
     /**
-     * Returns a list of descriptors for all hardware modules loaded.
-     * @param opPackageName
-     * @param modules A ModuleProperties array where the list will be returned.
-     * @return - {@link #STATUS_OK} in case of success
-     *         - {@link #STATUS_ERROR} in case of unspecified error
-     *         - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
-     *         - {@link #STATUS_NO_INIT} if the native service cannot be reached
-     *         - {@link #STATUS_BAD_VALUE} if modules is null
-     *         - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
-     */
-    private static native int listModules(String opPackageName,
-                                          ArrayList<ModuleProperties> modules);
-
-    /**
      * Get an interface on a hardware module to control sound models and recognition on
      * this module.
      * @param moduleId Sound module system identifier {@link ModuleProperties#id}. mandatory.
@@ -1553,14 +1589,40 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public static SoundTriggerModule attachModule(int moduleId,
-                                                  StatusListener listener,
-                                                  Handler handler) {
-        if (listener == null) {
+    public static @NonNull SoundTriggerModule attachModule(int moduleId,
+            @NonNull StatusListener listener,
+            @Nullable Handler handler) {
+        Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
+        try {
+            return new SoundTriggerModule(getService(), moduleId, listener, looper);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
             return null;
         }
-        SoundTriggerModule module = new SoundTriggerModule(moduleId, listener, handler);
-        return module;
+    }
+
+    private static ISoundTriggerMiddlewareService getService() {
+        synchronized (mServiceLock) {
+            while (true) {
+                IBinder binder = null;
+                try {
+                    binder =
+                            ServiceManager.getServiceOrThrow(
+                                    Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE);
+                    binder.linkToDeath(() -> {
+                        synchronized (mServiceLock) {
+                            mService = null;
+                        }
+                    }, 0);
+                    mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder);
+                    break;
+                } catch (Exception e) {
+                    Log.e(TAG, "Failed to bind to soundtrigger service", e);
+                }
+            }
+            return  mService;
+        }
+
     }
 
     /**
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index b16ef5c..7cf5600 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -16,14 +16,23 @@
 
 package android.hardware.soundtrigger;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
-import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.SoundModel;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-
-import java.lang.ref.WeakReference;
+import android.os.RemoteException;
+import android.util.Log;
 
 /**
  * The SoundTriggerModule provides APIs to control sound models and sound detection
@@ -32,39 +41,47 @@
  * @hide
  */
 public class SoundTriggerModule {
-    @UnsupportedAppUsage
-    private long mNativeContext;
+    private static final String TAG = "SoundTriggerModule";
 
-    @UnsupportedAppUsage
-    private int mId;
-    private NativeEventHandlerDelegate mEventHandlerDelegate;
-
-    // to be kept in sync with core/jni/android_hardware_SoundTrigger.cpp
     private static final int EVENT_RECOGNITION = 1;
     private static final int EVENT_SERVICE_DIED = 2;
-    private static final int EVENT_SOUNDMODEL = 3;
-    private static final int EVENT_SERVICE_STATE_CHANGE = 4;
+    private static final int EVENT_SERVICE_STATE_CHANGE = 3;
+    @UnsupportedAppUsage
+    private int mId;
+    private EventHandlerDelegate mEventHandlerDelegate;
+    private ISoundTriggerModule mService;
 
-    SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) {
+    SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
+            int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper)
+            throws RemoteException {
         mId = moduleId;
-        mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler);
-        native_setup(SoundTrigger.getCurrentOpPackageName(),
-                new WeakReference<SoundTriggerModule>(this));
+        mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
+        mService = service.attach(moduleId, mEventHandlerDelegate);
+        mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
     }
-    private native void native_setup(String opPackageName, Object moduleThis);
 
     @Override
     protected void finalize() {
-        native_finalize();
+        detach();
     }
-    private native void native_finalize();
 
     /**
      * Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called
      * anymore and associated resources will be released.
-     * */
+     * All models must have been unloaded prior to detaching.
+     */
     @UnsupportedAppUsage
-    public native void detach();
+    public synchronized void detach() {
+        try {
+            if (mService != null) {
+                mService.asBinder().unlinkToDeath(mEventHandlerDelegate, 0);
+                mService.detach();
+                mService = null;
+            }
+        } catch (Exception e) {
+            handleException(e);
+        }
+    }
 
     /**
      * Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in
@@ -82,7 +99,26 @@
      *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
      */
     @UnsupportedAppUsage
-    public native int loadSoundModel(SoundTrigger.SoundModel model, int[] soundModelHandle);
+    public synchronized int loadSoundModel(@NonNull SoundTrigger.SoundModel model,
+            @NonNull int[] soundModelHandle) {
+        try {
+            if (model instanceof SoundTrigger.GenericSoundModel) {
+                SoundModel aidlModel = ConversionUtil.api2aidlGenericSoundModel(
+                        (SoundTrigger.GenericSoundModel) model);
+                soundModelHandle[0] = mService.loadModel(aidlModel);
+                return SoundTrigger.STATUS_OK;
+            }
+            if (model instanceof SoundTrigger.KeyphraseSoundModel) {
+                PhraseSoundModel aidlModel = ConversionUtil.api2aidlPhraseSoundModel(
+                        (SoundTrigger.KeyphraseSoundModel) model);
+                soundModelHandle[0] = mService.loadPhraseModel(aidlModel);
+                return SoundTrigger.STATUS_OK;
+            }
+            return SoundTrigger.STATUS_BAD_VALUE;
+        } catch (Exception e) {
+            return handleException(e);
+        }
+    }
 
     /**
      * Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition
@@ -97,7 +133,14 @@
      *         service fails
      */
     @UnsupportedAppUsage
-    public native int unloadSoundModel(int soundModelHandle);
+    public synchronized int unloadSoundModel(int soundModelHandle) {
+        try {
+            mService.unloadModel(soundModelHandle);
+            return SoundTrigger.STATUS_OK;
+        } catch (Exception e) {
+            return handleException(e);
+        }
+    }
 
     /**
      * Start listening to all key phrases in a {@link SoundTrigger.SoundModel}.
@@ -117,7 +160,16 @@
      *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
      */
     @UnsupportedAppUsage
-    public native int startRecognition(int soundModelHandle, SoundTrigger.RecognitionConfig config);
+    public synchronized int startRecognition(int soundModelHandle,
+            SoundTrigger.RecognitionConfig config) {
+        try {
+            mService.startRecognition(soundModelHandle,
+                    ConversionUtil.api2aidlRecognitionConfig(config));
+            return SoundTrigger.STATUS_OK;
+        } catch (Exception e) {
+            return handleException(e);
+        }
+    }
 
     /**
      * Stop listening to all key phrases in a {@link SoundTrigger.SoundModel}
@@ -133,12 +185,20 @@
      *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
      */
     @UnsupportedAppUsage
-    public native int stopRecognition(int soundModelHandle);
+    public synchronized int stopRecognition(int soundModelHandle) {
+        try {
+            mService.stopRecognition(soundModelHandle);
+            return SoundTrigger.STATUS_OK;
+        } catch (Exception e) {
+            return handleException(e);
+        }
+    }
 
     /**
      * Get the current state of a {@link SoundTrigger.SoundModel}.
-     * The state will be returned asynchronously as a {@link SoundTrigger#RecognitionEvent}
-     * in the callback registered in the {@link SoundTrigger.startRecognition} method.
+     * The state will be returned asynchronously as a {@link SoundTrigger.RecognitionEvent}
+     * in the callback registered in the
+     * {@link SoundTrigger#attachModule(int, SoundTrigger.StatusListener, Handler)} method.
      * @param soundModelHandle The sound model handle indicating which model's state to return
      * @return - {@link SoundTrigger#STATUS_OK} in case of success
      *         - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
@@ -150,46 +210,71 @@
      *         service fails
      *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
      */
-    public native int getModelState(int soundModelHandle);
+    public synchronized int getModelState(int soundModelHandle) {
+        try {
+            mService.forceRecognitionEvent(soundModelHandle);
+            return SoundTrigger.STATUS_OK;
+        } catch (Exception e) {
+            return handleException(e);
+        }
+    }
 
     /**
      * 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
+     * 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 SoundTriggerModule#isParameterSupported} should be checked first before calling this
-     * method.
+     * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before calling
+     * this method.
      *
      * @param soundModelHandle handle of model to apply parameter
-     * @param modelParam   {@link ModelParams}
-     * @param value        Value to set
+     * @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
+     * - {@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 native int setParameter(int soundModelHandle,
-            @ModelParams int modelParam, int value);
+    public synchronized int setParameter(int soundModelHandle, @ModelParams int modelParam,
+            int value) {
+        try {
+            mService.setModelParameter(soundModelHandle,
+                    ConversionUtil.api2aidlModelParameter(modelParam), value);
+            return SoundTrigger.STATUS_OK;
+        } catch (Exception e) {
+            return handleException(e);
+        }
+    }
 
     /**
      * 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 SoundTriggerModule#isParameterSupported} should be checked first before
+     * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before
      * calling this method. Otherwise, an exception can be thrown.
      *
      * @param soundModelHandle handle of model to get parameter
-     * @param modelParam   {@link ModelParams}
+     * @param modelParam       {@link ModelParams}
      * @return value of parameter
      * @throws UnsupportedOperationException if hal or model do not support this API.
-     *         {@link SoundTriggerModule#isParameterSupported} should be checked first.
-     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
-     *         {@link SoundTriggerModule#isParameterSupported} should be checked first.
+     *                                       {@link SoundTriggerModule#queryParameter(int, int)}
+     *                                       should
+     *                                       be checked first.
+     * @throws IllegalArgumentException      if invalid model handle or parameter is passed.
+     *                                       {@link SoundTriggerModule#queryParameter(int, int)}
+     *                                       should be checked first.
      */
-    public native int getParameter(int soundModelHandle,
-            @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException;
+    public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam)
+            throws UnsupportedOperationException, IllegalArgumentException {
+        try {
+            return mService.getModelParameter(soundModelHandle,
+                    ConversionUtil.api2aidlModelParameter(modelParam));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Determine if parameter control is supported for the given model handle.
@@ -197,85 +282,98 @@
      * {@link SoundTriggerModule#getParameter}.
      *
      * @param soundModelHandle handle of model to get parameter
-     * @param modelParam {@link ModelParams}
+     * @param modelParam       {@link ModelParams}
      * @return supported range of parameter, null if not supported
      */
     @Nullable
-    public native ModelParamRange queryParameter(int soundModelHandle, @ModelParams int modelParam);
-
-    private class NativeEventHandlerDelegate {
-        private final Handler mHandler;
-
-        NativeEventHandlerDelegate(final SoundTrigger.StatusListener listener,
-                                   Handler handler) {
-            // find the looper for our new event handler
-            Looper looper;
-            if (handler != null) {
-                looper = handler.getLooper();
-            } else {
-                looper = Looper.getMainLooper();
-            }
-
-            // construct the event handler with this looper
-            if (looper != null) {
-                // implement the event handler delegate
-                mHandler = new Handler(looper) {
-                    @Override
-                    public void handleMessage(Message msg) {
-                        switch(msg.what) {
-                        case EVENT_RECOGNITION:
-                            if (listener != null) {
-                                listener.onRecognition(
-                                        (SoundTrigger.RecognitionEvent)msg.obj);
-                            }
-                            break;
-                        case EVENT_SOUNDMODEL:
-                            if (listener != null) {
-                                listener.onSoundModelUpdate(
-                                        (SoundTrigger.SoundModelEvent)msg.obj);
-                            }
-                            break;
-                        case EVENT_SERVICE_STATE_CHANGE:
-                            if (listener != null) {
-                                listener.onServiceStateChange(msg.arg1);
-                            }
-                            break;
-                        case EVENT_SERVICE_DIED:
-                            if (listener != null) {
-                                listener.onServiceDied();
-                            }
-                            break;
-                        default:
-                            break;
-                        }
-                    }
-                };
-            } else {
-                mHandler = null;
-            }
-        }
-
-        Handler handler() {
-            return mHandler;
+    public synchronized SoundTrigger.ModelParamRange queryParameter(int soundModelHandle,
+            @ModelParams int modelParam) {
+        try {
+            return ConversionUtil.aidl2apiModelParameterRange(mService.queryModelParameterSupport(
+                    soundModelHandle,
+                    ConversionUtil.api2aidlModelParameter(modelParam)));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
-    @SuppressWarnings("unused")
-    @UnsupportedAppUsage
-    private static void postEventFromNative(Object module_ref,
-                                            int what, int arg1, int arg2, Object obj) {
-        SoundTriggerModule module = (SoundTriggerModule)((WeakReference)module_ref).get();
-        if (module == null) {
-            return;
+    private int handleException(Exception e) {
+        Log.e(TAG, "", e);
+        if (e instanceof NullPointerException) {
+            return SoundTrigger.STATUS_NO_INIT;
+        }
+        if (e instanceof RemoteException) {
+            return SoundTrigger.STATUS_DEAD_OBJECT;
+        }
+        if (e instanceof IllegalArgumentException) {
+            return SoundTrigger.STATUS_BAD_VALUE;
+        }
+        if (e instanceof IllegalStateException) {
+            return SoundTrigger.STATUS_INVALID_OPERATION;
+        }
+        return SoundTrigger.STATUS_ERROR;
+    }
+
+    private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements
+            IBinder.DeathRecipient {
+        private final Handler mHandler;
+
+        EventHandlerDelegate(@NonNull final SoundTrigger.StatusListener listener,
+                @NonNull Looper looper) {
+
+            // construct the event handler with this looper
+            // implement the event handler delegate
+            mHandler = new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case EVENT_RECOGNITION:
+                            listener.onRecognition(
+                                    (SoundTrigger.RecognitionEvent) msg.obj);
+                            break;
+                        case EVENT_SERVICE_STATE_CHANGE:
+                            listener.onServiceStateChange(msg.arg1);
+                            break;
+                        case EVENT_SERVICE_DIED:
+                            listener.onServiceDied();
+                            break;
+                        default:
+                            Log.e(TAG, "Unknown message: " + msg.toString());
+                            break;
+                    }
+                }
+            };
         }
 
-        NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate;
-        if (delegate != null) {
-            Handler handler = delegate.handler();
-            if (handler != null) {
-                Message m = handler.obtainMessage(what, arg1, arg2, obj);
-                handler.sendMessage(m);
-            }
+        @Override
+        public synchronized void onRecognition(int handle, RecognitionEvent event)
+                throws RemoteException {
+            Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+                    ConversionUtil.aidl2apiRecognitionEvent(handle, event));
+            mHandler.sendMessage(m);
+        }
+
+        @Override
+        public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event)
+                throws RemoteException {
+            Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+                    ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, event));
+            mHandler.sendMessage(m);
+        }
+
+        @Override
+        public synchronized void onRecognitionAvailabilityChange(boolean available)
+                throws RemoteException {
+            Message m = mHandler.obtainMessage(EVENT_SERVICE_STATE_CHANGE,
+                    available ? SoundTrigger.SERVICE_STATE_ENABLED
+                            : SoundTrigger.SERVICE_STATE_DISABLED);
+            mHandler.sendMessage(m);
+        }
+
+        @Override
+        public synchronized void binderDied() {
+            Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+            mHandler.sendMessage(m);
         }
     }
 }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index a47f601..62f0196 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -19,6 +19,7 @@
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
 import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
@@ -28,6 +29,7 @@
 import android.os.ResultReceiver;
 import android.util.Log;
 import android.view.InputChannel;
+import android.view.autofill.AutofillId;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
@@ -39,6 +41,7 @@
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethod;
 import com.android.internal.view.IInputMethodSession;
@@ -72,6 +75,7 @@
     private static final int DO_SHOW_SOFT_INPUT = 60;
     private static final int DO_HIDE_SOFT_INPUT = 70;
     private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
+    private static final int DO_CREATE_INLINE_SUGGESTIONS_REQUEST = 90;
 
     final WeakReference<AbstractInputMethodService> mTarget;
     final Context mContext;
@@ -225,6 +229,11 @@
             case DO_CHANGE_INPUTMETHOD_SUBTYPE:
                 inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj);
                 return;
+            case DO_CREATE_INLINE_SUGGESTIONS_REQUEST:
+                SomeArgs args = (SomeArgs) msg.obj;
+                inputMethod.onCreateInlineSuggestionsRequest((ComponentName) args.arg1,
+                        (AutofillId) args.arg2, (IInlineSuggestionsRequestCallback) args.arg3);
+                return;
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
@@ -267,6 +276,15 @@
 
     @BinderThread
     @Override
+    public void onCreateInlineSuggestionsRequest(ComponentName componentName, AutofillId autofillId,
+            IInlineSuggestionsRequestCallback cb) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOOO(DO_CREATE_INLINE_SUGGESTIONS_REQUEST, componentName,
+                        autofillId, cb));
+    }
+
+    @BinderThread
+    @Override
     public void bindInput(InputBinding binding) {
         if (mIsUnbindIssued != null) {
             Log.e(TAG, "bindInput must be paired with unbindInput.");
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 156bcfe..7da7dc1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -23,6 +23,8 @@
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ONLY_DRAW_BOTTOM_BAR_BACKGROUND;
 
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.AnyThread;
@@ -35,6 +37,7 @@
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityManager;
 import android.app.Dialog;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -47,6 +50,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.provider.Settings;
@@ -70,11 +75,14 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.animation.AnimationUtils;
+import android.view.autofill.AutofillId;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
@@ -91,11 +99,14 @@
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
 import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.Collections;
 
 /**
@@ -436,6 +447,14 @@
     final Insets mTmpInsets = new Insets();
     final int[] mTmpLocation = new int[2];
 
+    @Nullable
+    private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null;
+
+    @Nullable
+    private InlineSuggestionsResponseCallbackImpl mInlineSuggestionsResponseCallback = null;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
     final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
         onComputeInsets(mTmpInsets);
         if (isExtractViewShown()) {
@@ -495,6 +514,18 @@
 
         /**
          * {@inheritDoc}
+         * @hide
+         */
+        @MainThread
+        @Override
+        public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+                AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+            Log.d(TAG, "InputMethodService received onCreateInlineSuggestionsRequest()");
+            handleOnCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+        }
+
+        /**
+         * {@inheritDoc}
          */
         @MainThread
         @Override
@@ -670,6 +701,103 @@
         }
     }
 
+    // TODO(b/137800469): Add detailed docs explaining the inline suggestions process.
+    /**
+     * Returns an {@link InlineSuggestionsRequest} to be sent to Autofill.
+     *
+     * <p>Should be implemented by subclasses.</p>
+     */
+    public @Nullable InlineSuggestionsRequest onCreateInlineSuggestionsRequest() {
+        return null;
+    }
+
+    /**
+     * Called when Autofill responds back with {@link InlineSuggestionsResponse} containing
+     * inline suggestions.
+     *
+     * <p>Should be implemented by subclasses.</p>
+     *
+     * @param response {@link InlineSuggestionsResponse} passed back by Autofill.
+     * @return Whether the IME will use and render  the inline suggestions.
+     */
+    public boolean onInlineSuggestionsResponse(@NonNull InlineSuggestionsResponse response) {
+        return false;
+    }
+
+    /**
+     * Returns whether inline suggestions are enabled on this service.
+     *
+     * TODO(b/137800469): check XML for value.
+     */
+    private boolean isInlineSuggestionsEnabled() {
+        return true;
+    }
+
+    /**
+     * Sends an {@link InlineSuggestionsRequest} obtained from
+     * {@link #onCreateInlineSuggestionsRequest()} to the current Autofill Session through
+     * {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest}.
+     */
+    private void makeInlineSuggestionsRequest() {
+        if (mInlineSuggestionsRequestInfo == null) {
+            Log.w(TAG, "makeInlineSuggestionsRequest() called with null requestInfo cache");
+            return;
+        }
+
+        final IInlineSuggestionsRequestCallback requestCallback =
+                mInlineSuggestionsRequestInfo.mCallback;
+        try {
+            final InlineSuggestionsRequest request = onCreateInlineSuggestionsRequest();
+            if (request == null) {
+                Log.w(TAG, "onCreateInlineSuggestionsRequest() returned null request");
+                requestCallback.onInlineSuggestionsUnsupported();
+            } else {
+                if (mInlineSuggestionsResponseCallback == null) {
+                    mInlineSuggestionsResponseCallback =
+                            new InlineSuggestionsResponseCallbackImpl(this,
+                                    mInlineSuggestionsRequestInfo.mComponentName,
+                                    mInlineSuggestionsRequestInfo.mFocusedId);
+                }
+                requestCallback.onInlineSuggestionsRequest(request,
+                        mInlineSuggestionsResponseCallback);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "makeInlinedSuggestionsRequest() remote exception:" + e);
+        }
+    }
+
+    private void handleOnCreateInlineSuggestionsRequest(@NonNull ComponentName componentName,
+            @NonNull AutofillId autofillId, @NonNull IInlineSuggestionsRequestCallback callback) {
+        mInlineSuggestionsRequestInfo = new InlineSuggestionsRequestInfo(componentName, autofillId,
+                callback);
+
+        if (!isInlineSuggestionsEnabled()) {
+            try {
+                callback.onInlineSuggestionsUnsupported();
+            } catch (RemoteException e) {
+                Log.w(TAG, "handleMakeInlineSuggestionsRequest() RemoteException:" + e);
+            }
+            return;
+        }
+
+        if (!mInputStarted) {
+            Log.w(TAG, "onStartInput() not called yet");
+            return;
+        }
+
+        makeInlineSuggestionsRequest();
+    }
+
+    private void handleOnInlineSuggestionsResponse(@NonNull ComponentName componentName,
+            @NonNull AutofillId autofillId, @NonNull InlineSuggestionsResponse response) {
+        if (!mInlineSuggestionsRequestInfo.validate(componentName)) {
+            Log.d(TAG, "Response component=" + componentName + " differs from request component="
+                    + mInlineSuggestionsRequestInfo.mComponentName + ", ignoring response");
+            return;
+        }
+        onInlineSuggestionsResponse(response);
+    }
+
     private void notifyImeHidden() {
         setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
         onPreRenderedWindowVisibilityChanged(false /* setVisible */);
@@ -688,6 +816,63 @@
     }
 
     /**
+     * Internal implementation of {@link IInlineSuggestionsResponseCallback}.
+     */
+    private static final class InlineSuggestionsResponseCallbackImpl
+            extends IInlineSuggestionsResponseCallback.Stub {
+        private final WeakReference<InputMethodService> mInputMethodService;
+
+        private final ComponentName mRequestComponentName;
+        private final AutofillId mRequestAutofillId;
+
+        private InlineSuggestionsResponseCallbackImpl(InputMethodService inputMethodService,
+                ComponentName componentName, AutofillId autofillId) {
+            mInputMethodService = new WeakReference<>(inputMethodService);
+            mRequestComponentName = componentName;
+            mRequestAutofillId = autofillId;
+        }
+
+        @Override
+        public void onInlineSuggestionsResponse(InlineSuggestionsResponse response)
+                throws RemoteException {
+            final InputMethodService service = mInputMethodService.get();
+            if (service != null) {
+                service.mHandler.sendMessage(obtainMessage(
+                        InputMethodService::handleOnInlineSuggestionsResponse, service,
+                        mRequestComponentName, mRequestAutofillId, response));
+            }
+        }
+    }
+
+    /**
+     * Information about incoming requests from Autofill Frameworks for inline suggestions.
+     */
+    private static final class InlineSuggestionsRequestInfo {
+        final ComponentName mComponentName;
+        final AutofillId mFocusedId;
+        final IInlineSuggestionsRequestCallback mCallback;
+
+        InlineSuggestionsRequestInfo(ComponentName componentName, AutofillId focusedId,
+                IInlineSuggestionsRequestCallback callback) {
+            this.mComponentName = componentName;
+            this.mFocusedId = focusedId;
+            this.mCallback = callback;
+        }
+
+        /**
+         * Returns whether the cached {@link ComponentName} matches the passed in activity.
+         */
+        public boolean validate(ComponentName componentName) {
+            final boolean result = componentName.equals(mComponentName);
+            if (!result) {
+                Log.d(TAG, "Cached request info ComponentName=" + mComponentName
+                        + " differs from received ComponentName=" + componentName);
+            }
+            return result;
+        }
+    }
+
+    /**
      * Concrete implementation of
      * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
      * all of the standard behavior for an input method session.
diff --git a/core/java/android/net/InvalidPacketException.java b/core/java/android/net/InvalidPacketException.java
new file mode 100644
index 0000000..909998d
--- /dev/null
+++ b/core/java/android/net/InvalidPacketException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Thrown when a packet is invalid.
+ * @hide
+ */
+@SystemApi
+public class InvalidPacketException extends Exception {
+    public final int error;
+
+    // Must match SocketKeepalive#ERROR_INVALID_IP_ADDRESS.
+    /** Invalid IP address. */
+    public static final int ERROR_INVALID_IP_ADDRESS = -21;
+
+    // Must match SocketKeepalive#ERROR_INVALID_PORT.
+    /** Invalid port number. */
+    public static final int ERROR_INVALID_PORT = -22;
+
+    // Must match SocketKeepalive#ERROR_INVALID_LENGTH.
+    /** Invalid packet length. */
+    public static final int ERROR_INVALID_LENGTH = -23;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "ERROR_" }, value = {
+        ERROR_INVALID_IP_ADDRESS,
+        ERROR_INVALID_PORT,
+        ERROR_INVALID_LENGTH
+    })
+    public @interface ErrorCode {}
+
+    /**
+     * This packet is invalid.
+     * See the error code for details.
+     */
+    public InvalidPacketException(@ErrorCode final int error) {
+        this.error = error;
+    }
+}
diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java
index 9b8b732..2b8b7e6 100644
--- a/core/java/android/net/KeepalivePacketData.java
+++ b/core/java/android/net/KeepalivePacketData.java
@@ -16,13 +16,13 @@
 
 package android.net;
 
-import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
-import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS;
+import static android.net.InvalidPacketException.ERROR_INVALID_PORT;
 
-import android.net.SocketKeepalive.InvalidPacketException;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.net.util.IpUtils;
 import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.Log;
 
 import java.net.InetAddress;
@@ -33,13 +33,16 @@
  *
  * @hide
  */
-public class KeepalivePacketData implements Parcelable {
+@SystemApi
+public class KeepalivePacketData {
     private static final String TAG = "KeepalivePacketData";
 
     /** Source IP address */
+    @NonNull
     public final InetAddress srcAddress;
 
     /** Destination IP address */
+    @NonNull
     public final InetAddress dstAddress;
 
     /** Source port */
@@ -51,13 +54,14 @@
     /** Packet data. A raw byte string of packet data, not including the link-layer header. */
     private final byte[] mPacket;
 
-    protected static final int IPV4_HEADER_LENGTH = 20;
-    protected static final int UDP_HEADER_LENGTH = 8;
-
     // This should only be constructed via static factory methods, such as
-    // nattKeepalivePacket
-    protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
-            InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
+    // nattKeepalivePacket.
+    /**
+     * A holding class for data necessary to build a keepalive packet.
+     */
+    protected KeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort,
+            @NonNull InetAddress dstAddress, int dstPort,
+                    @NonNull byte[] data) throws InvalidPacketException {
         this.srcAddress = srcAddress;
         this.dstAddress = dstAddress;
         this.srcPort = srcPort;
@@ -78,16 +82,12 @@
         }
     }
 
+    @NonNull
     public byte[] getPacket() {
         return mPacket.clone();
     }
 
-    /* Parcelable Implementation */
-    public int describeContents() {
-        return 0;
-    }
-
-    /** Write to parcel */
+    /** @hide */
     public void writeToParcel(Parcel out, int flags) {
         out.writeString(srcAddress.getHostAddress());
         out.writeString(dstAddress.getHostAddress());
@@ -96,6 +96,7 @@
         out.writeByteArray(mPacket);
     }
 
+    /** @hide */
     protected KeepalivePacketData(Parcel in) {
         srcAddress = NetworkUtils.numericToInetAddress(in.readString());
         dstAddress = NetworkUtils.numericToInetAddress(in.readString());
@@ -103,17 +104,4 @@
         dstPort = in.readInt();
         mPacket = in.createByteArray();
     }
-
-    /** Parcelable Creator */
-    public static final @android.annotation.NonNull Parcelable.Creator<KeepalivePacketData> CREATOR =
-            new Parcelable.Creator<KeepalivePacketData>() {
-                public KeepalivePacketData createFromParcel(Parcel in) {
-                    return new KeepalivePacketData(in);
-                }
-
-                public KeepalivePacketData[] newArray(int size) {
-                    return new KeepalivePacketData[size];
-                }
-            };
-
 }
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
index a77c244..3fb52f1 100644
--- a/core/java/android/net/NattKeepalivePacketData.java
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -19,7 +19,6 @@
 import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
 import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
 
-import android.net.SocketKeepalive.InvalidPacketException;
 import android.net.util.IpUtils;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -32,6 +31,9 @@
 
 /** @hide */
 public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
+    private static final int IPV4_HEADER_LENGTH = 20;
+    private static final int UDP_HEADER_LENGTH = 8;
+
     // This should only be constructed via static factory methods, such as
     // nattKeepalivePacket
     private NattKeepalivePacketData(InetAddress srcAddress, int srcPort,
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index ec73866..fb224fb 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -147,17 +147,6 @@
         }
     }
 
-    /**
-     * This packet is invalid.
-     * See the error code for details.
-     * @hide
-     */
-    public static class InvalidPacketException extends ErrorCodeException {
-        public InvalidPacketException(final int error) {
-            super(error);
-        }
-    }
-
     @NonNull final IConnectivityManager mService;
     @NonNull final Network mNetwork;
     @NonNull final ParcelFileDescriptor mPfd;
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index a92237b..61da5e6 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -34,6 +34,8 @@
 import android.util.Log;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.LinkedList;
 
 /**
@@ -528,6 +530,22 @@
     }
 
     /**
+     * Return locations where media files (such as ringtones, notification
+     * sounds, or alarm sounds) may be located on internal storage. These are
+     * typically indexed under {@link MediaStore#VOLUME_INTERNAL}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static @NonNull Collection<File> getInternalMediaDirectories() {
+        final ArrayList<File> res = new ArrayList<>();
+        res.add(new File(Environment.getRootDirectory(), "media"));
+        res.add(new File(Environment.getOemDirectory(), "media"));
+        res.add(new File(Environment.getProductDirectory(), "media"));
+        return res;
+    }
+
+    /**
      * Return the primary shared/external storage directory. This directory may
      * not currently be accessible if it has been mounted by the user on their
      * computer, has been removed from the device, or some other problem has
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 12bce8a..ed980f3 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -182,6 +182,14 @@
     public static final int MAX_IPC_SIZE = 64 * 1024;
 
     /**
+     * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction
+     * buffer limit.
+     */
+    static int getSuggestedMaxIpcSizeBytes() {
+        return MAX_IPC_SIZE;
+    }
+
+    /**
      * Get the canonical name of the interface supported by this binder.
      */
     public @Nullable String getInterfaceDescriptor() throws RemoteException;
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 1456ff7..6b881fe 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -24,6 +24,7 @@
 {
     boolean hasVibrator();
     boolean hasAmplitudeControl();
+    boolean setAlwaysOnEffect(int id, in VibrationEffect effect, in AudioAttributes attributes);
     void vibrate(int uid, String opPkg, in VibrationEffect effect, in AudioAttributes attributes,
             String reason, IBinder token);
     void cancelVibrate(IBinder token);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 9eb6445..339397b 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1815,8 +1815,12 @@
         p.writeToParcel(this, parcelableFlags);
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * Flatten the name of the class of the Parcelable into this Parcel.
+     *
+     * @param p The Parcelable object to be written.
+     * @see #readParcelableCreator
+     */
     public final void writeParcelableCreator(@NonNull Parcelable p) {
         String name = p.getClass().getName();
         writeString(name);
@@ -3011,8 +3015,19 @@
         return (T) creator.createFromParcel(this);
     }
 
-    /** @hide */
-    @UnsupportedAppUsage
+    /**
+     * Read and return a Parcelable.Creator from the parcel. The given class loader will be used to
+     * load the {@link Parcelable.Creator}. If it is null, the default class loader will be used.
+     *
+     * @param loader A ClassLoader from which to instantiate the {@link Parcelable.Creator}
+     * object, or null for the default class loader.
+     * @return the previously written {@link Parcelable.Creator}, or null if a null Creator was
+     * written.
+     * @throws BadParcelableException Throws BadParcelableException if there was an error trying to
+     * read the {@link Parcelable.Creator}.
+     *
+     * @see #writeParcelableCreator
+     */
     @Nullable
     public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
         String name = readString();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 725e0fb..5e478b5 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -44,70 +44,10 @@
  * <p>
  * <b>Device battery life will be significantly affected by the use of this API.</b>
  * Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
- * possible, and be sure to release them as soon as possible.
- * </p><p>
- * The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.
- * This will create a {@link PowerManager.WakeLock} object.  You can then use methods
- * on the wake lock object to control the power state of the device.
- * </p><p>
- * In practice it's quite simple:
- * {@samplecode
- * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- * PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
- * wl.acquire();
- *   ..screen will stay on during this section..
- * wl.release();
- * }
- * </p><p>
- * The following wake lock levels are defined, with varying effects on system power.
- * <i>These levels are mutually exclusive - you may only specify one of them.</i>
+ * possible, and be sure to release them as soon as possible. In most cases,
+ * you'll want to use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
  *
- * <table>
- *     <tr><th>Flag Value</th>
- *     <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
- *
- *     <tr><td>{@link #PARTIAL_WAKE_LOCK}</td>
- *         <td>On*</td> <td>Off</td> <td>Off</td>
- *     </tr>
- *
- *     <tr><td>{@link #SCREEN_DIM_WAKE_LOCK}</td>
- *         <td>On</td> <td>Dim</td> <td>Off</td>
- *     </tr>
- *
- *     <tr><td>{@link #SCREEN_BRIGHT_WAKE_LOCK}</td>
- *         <td>On</td> <td>Bright</td> <td>Off</td>
- *     </tr>
- *
- *     <tr><td>{@link #FULL_WAKE_LOCK}</td>
- *         <td>On</td> <td>Bright</td> <td>Bright</td>
- *     </tr>
- * </table>
- * </p><p>
- * *<i>If you hold a partial wake lock, the CPU will continue to run, regardless of any
- * display timeouts or the state of the screen and even after the user presses the power button.
- * In all other wake locks, the CPU will run, but the user can still put the device to sleep
- * using the power button.</i>
- * </p><p>
- * In addition, you can add two more flags, which affect behavior of the screen only.
- * <i>These flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i></p>
- *
- * <table>
- *     <tr><th>Flag Value</th> <th>Description</th></tr>
- *
- *     <tr><td>{@link #ACQUIRE_CAUSES_WAKEUP}</td>
- *         <td>Normal wake locks don't actually turn on the illumination.  Instead, they cause
- *         the illumination to remain on once it turns on (e.g. from user activity).  This flag
- *         will force the screen and/or keyboard to turn on immediately, when the WakeLock is
- *         acquired.  A typical use would be for notifications which are important for the user to
- *         see immediately.</td>
- *     </tr>
- *
- *     <tr><td>{@link #ON_AFTER_RELEASE}</td>
- *         <td>If this flag is set, the user activity timer will be reset when the WakeLock is
- *         released, causing the illumination to remain on a bit longer.  This can be used to
- *         reduce flicker if you are cycling between wake lock conditions.</td>
- *     </tr>
- * </table>
  * <p>
  * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
  * permission in an {@code <uses-permission>} element of the application's manifest.
@@ -931,7 +871,8 @@
      * {@link #FULL_WAKE_LOCK}, {@link #SCREEN_DIM_WAKE_LOCK}
      * and {@link #SCREEN_BRIGHT_WAKE_LOCK}.  Exactly one wake lock level must be
      * specified as part of the {@code levelAndFlags} parameter.
-     * </p><p>
+     * </p>
+     * <p>
      * The wake lock flags are: {@link #ACQUIRE_CAUSES_WAKEUP}
      * and {@link #ON_AFTER_RELEASE}.  Multiple flags can be combined as part of the
      * {@code levelAndFlags} parameters.
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index a5188e7..fbd11ca 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -70,6 +70,20 @@
     }
 
     @Override
+    public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attributes) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
+            return false;
+        }
+        try {
+            return mService.setAlwaysOnEffect(id, effect, attributes);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to set always-on effect.", e);
+        }
+        return false;
+    }
+
+    @Override
     public void vibrate(int uid, String opPkg, VibrationEffect effect,
             String reason, AudioAttributes attributes) {
         if (mService == null) {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 28909c8..6456d72 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.UnsupportedAppUsage;
@@ -152,6 +153,24 @@
     public abstract boolean hasAmplitudeControl();
 
     /**
+     * Configure an always-on haptics effect.
+     *
+     * @param id The board-specific always-on ID to configure.
+     * @param effect Vibration effect to assign to always-on id. Passing null will disable it.
+     * @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
+     *        specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
+     *        {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
+     *        vibrations associated with incoming calls. May only be null when effect is null.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+    public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect,
+                                  @Nullable AudioAttributes attributes) {
+        Log.w(TAG, "Always-on effects aren't supported");
+        return false;
+    }
+
+    /**
      * Vibrate constantly for the specified period of time.
      *
      * @param milliseconds The number of milliseconds to vibrate.
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 62603fe..2e9f27e 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -264,6 +264,8 @@
     public static final int FLAG_REAL_STATE = 1 << 9;
     /** {@hide} */
     public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
+    /** {@hide} */
+    public static final int FLAG_INCLUDE_RECENT = 1 << 11;
 
     /** {@hide} */
     public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
@@ -1125,7 +1127,7 @@
      * Return the {@link StorageVolume} that contains the given file, or
      * {@code null} if none.
      */
-    public @Nullable StorageVolume getStorageVolume(File file) {
+    public @Nullable StorageVolume getStorageVolume(@NonNull File file) {
         return getStorageVolume(getVolumeList(), file);
     }
 
@@ -1140,7 +1142,7 @@
                 return getPrimaryStorageVolume();
             default:
                 for (StorageVolume vol : getStorageVolumes()) {
-                    if (Objects.equals(vol.getNormalizedUuid(), volumeName)) {
+                    if (Objects.equals(vol.getMediaStoreVolumeName(), volumeName)) {
                         return vol;
                     }
                 }
@@ -1201,12 +1203,13 @@
     }
 
     /**
-     * Return the list of shared/external storage volumes available to the
-     * current user. This includes both the primary shared storage device and
-     * any attached external volumes including SD cards and USB drives.
-     *
-     * @see Environment#getExternalStorageDirectory()
-     * @see StorageVolume#createAccessIntent(String)
+     * Return the list of shared/external storage volumes currently available to
+     * the calling user.
+     * <p>
+     * These storage volumes are actively attached to the device, but may be in
+     * any mount state, as returned by {@link StorageVolume#getState()}. Returns
+     * both the primary shared storage device and any attached external volumes,
+     * including SD cards and USB drives.
      */
     public @NonNull List<StorageVolume> getStorageVolumes() {
         final ArrayList<StorageVolume> res = new ArrayList<>();
@@ -1216,6 +1219,22 @@
     }
 
     /**
+     * Return the list of shared/external storage volumes both currently and
+     * recently available to the calling user.
+     * <p>
+     * Recently available storage volumes are likely to reappear in the future,
+     * so apps are encouraged to preserve any indexed metadata related to these
+     * volumes to optimize user experiences.
+     */
+    public @NonNull List<StorageVolume> getRecentStorageVolumes() {
+        final ArrayList<StorageVolume> res = new ArrayList<>();
+        Collections.addAll(res,
+                getVolumeList(mContext.getUserId(),
+                        FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_RECENT));
+        return res;
+    }
+
+    /**
      * Return the primary shared/external storage volume available to the
      * current user. This volume is the same storage device returned by
      * {@link Environment#getExternalStorageDirectory()} and
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index aefe843..560d617 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -29,6 +29,7 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.provider.DocumentsContract;
+import android.provider.MediaStore;
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -173,7 +174,7 @@
      * @return the mount path
      * @hide
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
     @TestApi
     public String getPath() {
         return mPath.toString();
@@ -190,12 +191,35 @@
     }
 
     /** {@hide} */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
     public File getPathFile() {
         return mPath;
     }
 
     /**
+     * Returns the directory where this volume is currently mounted.
+     * <p>
+     * Direct filesystem access via this path has significant emulation
+     * overhead, and apps are instead strongly encouraged to interact with media
+     * on storage volumes via the {@link MediaStore} APIs.
+     * <p>
+     * This directory does not give apps any additional access beyond what they
+     * already have via {@link MediaStore}.
+     *
+     * @return directory where this volume is mounted, or {@code null} if the
+     *         volume is not currently mounted.
+     */
+    public @Nullable File getDirectory() {
+        switch (mState) {
+            case Environment.MEDIA_MOUNTED:
+            case Environment.MEDIA_MOUNTED_READ_ONLY:
+                return mPath;
+            default:
+                return null;
+        }
+    }
+
+    /**
      * Returns a user-visible description of the volume.
      *
      * @return the volume description
@@ -265,6 +289,24 @@
         return mFsUuid;
     }
 
+    /**
+     * Return the volume name that can be used to interact with this storage
+     * device through {@link MediaStore}.
+     *
+     * @return opaque volume name, or {@code null} if this volume is not indexed
+     *         by {@link MediaStore}.
+     * @see android.provider.MediaStore.Audio.Media#getContentUri(String)
+     * @see android.provider.MediaStore.Video.Media#getContentUri(String)
+     * @see android.provider.MediaStore.Images.Media#getContentUri(String)
+     */
+    public @Nullable String getMediaStoreVolumeName() {
+        if (isPrimary()) {
+            return MediaStore.VOLUME_EXTERNAL_PRIMARY;
+        } else {
+            return getNormalizedUuid();
+        }
+    }
+
     /** {@hide} */
     public static @Nullable String normalizeUuid(@Nullable String fsUuid) {
         return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java
index 1a794eb..99b45d6 100644
--- a/core/java/android/os/storage/VolumeRecord.java
+++ b/core/java/android/os/storage/VolumeRecord.java
@@ -17,14 +17,18 @@
 package android.os.storage;
 
 import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Environment;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.util.DebugUtils;
 import android.util.TimeUtils;
 
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 
+import java.io.File;
 import java.util.Locale;
 import java.util.Objects;
 
@@ -92,6 +96,27 @@
         return (userFlags & USER_FLAG_SNOOZED) != 0;
     }
 
+    public StorageVolume buildStorageVolume(Context context) {
+        final String id = "unknown:" + fsUuid;
+        final File userPath = new File("/dev/null");
+        final File internalPath = new File("/dev/null");
+        final boolean primary = false;
+        final boolean removable = true;
+        final boolean emulated = false;
+        final boolean allowMassStorage = false;
+        final long maxFileSize = 0;
+        final UserHandle user = new UserHandle(UserHandle.USER_NULL);
+        final String envState = Environment.MEDIA_UNKNOWN;
+
+        String description = nickname;
+        if (description == null) {
+            description = context.getString(android.R.string.unknownName);
+        }
+
+        return new StorageVolume(id, userPath, internalPath, description, primary, removable,
+                emulated, allowMassStorage, maxFileSize, user, fsUuid, envState);
+    }
+
     public void dump(IndentingPrintWriter pw) {
         pw.println("VolumeRecord:");
         pw.increaseIndent();
diff --git a/core/java/android/provider/BaseColumns.java b/core/java/android/provider/BaseColumns.java
index 00c9e72..b216e2b 100644
--- a/core/java/android/provider/BaseColumns.java
+++ b/core/java/android/provider/BaseColumns.java
@@ -16,13 +16,11 @@
 
 package android.provider;
 
-import android.database.Cursor;
-
 public interface BaseColumns {
     /**
      * The unique ID for a row.
      */
-    @Column(Cursor.FIELD_TYPE_INTEGER)
+    // @Column(Cursor.FIELD_TYPE_INTEGER)
     public static final String _ID = "_id";
 
     /**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index eb1684f..c86c83c 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -2381,7 +2381,11 @@
          * This id is provided by its own data source, and can be used to backup metadata
          * to the server.
          * This should be unique within each set of account_name/account_type/data_set
+         *
+         * @deprecated This column is no longer supported as of Android version
+         * {@link android.os.Build.VERSION_CODES#R}.
          */
+        @Deprecated
         public static final String BACKUP_ID = "backup_id";
 
         /**
@@ -2445,7 +2449,11 @@
          * Flag indicating that a raw contact's metadata has changed, and its metadata
          * needs to be synchronized by the server.
          * <P>Type: INTEGER (boolean)</P>
+         *
+         * @deprecated This column is no longer supported as of Android version
+         * {@link android.os.Build.VERSION_CODES#R}.
          */
+        @Deprecated
         public static final String METADATA_DIRTY = "metadata_dirty";
     }
 
@@ -4188,7 +4196,10 @@
          * Hash id on the data fields, used for backup and restore.
          *
          * @hide
+         * @deprecated This column is no longer supported as of Android version
+         * {@link android.os.Build.VERSION_CODES#R}.
          */
+        @Deprecated
         public static final String HASH_ID = "hash_id";
 
         /**
@@ -9495,7 +9506,10 @@
 
     /**
      * @hide
+     * @deprecated These columns are no longer supported as of Android version
+     * {@link android.os.Build.VERSION_CODES#R}.
      */
+    @Deprecated
     @SystemApi
     protected interface MetadataSyncColumns {
 
@@ -9602,7 +9616,10 @@
      * from server before it is merged into other CP2 tables.
      *
      * @hide
+     * @deprecated These columns are no longer supported as of Android version
+     * {@link android.os.Build.VERSION_CODES#R}.
      */
+    @Deprecated
     @SystemApi
     public static final class MetadataSync implements BaseColumns, MetadataSyncColumns {
 
@@ -9638,7 +9655,10 @@
 
     /**
      * @hide
+     * @deprecated These columns are no longer supported as of Android version
+     * {@link android.os.Build.VERSION_CODES#R}.
      */
+    @Deprecated
     @SystemApi
     protected interface MetadataSyncStateColumns {
 
@@ -9672,7 +9692,10 @@
      * sync state for a set of accounts.
      *
      * @hide
+     * @deprecated These columns are no longer supported as of Android version
+     * {@link android.os.Build.VERSION_CODES#R}.
      */
+    @Deprecated
     @SystemApi
     public static final class MetadataSyncState implements BaseColumns, MetadataSyncStateColumns {
 
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index ef22d70..6650cf2 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -350,6 +350,15 @@
     public static final String NAMESPACE_PRIVACY = "privacy";
 
     /**
+     * Namespace for biometrics related features
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final String NAMESPACE_BIOMETRICS = "biometrics";
+
+    /**
      * Permission related properties definitions.
      *
      * @hide
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 2fa3386..63204d3 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -21,17 +21,16 @@
 import android.annotation.CurrentTimeSecondsLong;
 import android.annotation.DurationMillisLong;
 import android.annotation.IntDef;
-import android.annotation.IntRange;
 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;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.Activity;
-import android.app.AppGlobals;
+import android.app.PendingIntent;
 import android.content.ClipData;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
@@ -44,39 +43,28 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.ImageDecoder;
-import android.graphics.Point;
 import android.graphics.PostProcessor;
 import android.media.ExifInterface;
-import android.media.MediaFile;
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.OperationCanceledException;
-import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
-import android.os.storage.VolumeInfo;
-import android.os.storage.VolumeRecord;
-import android.service.media.CameraPrewarmService;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
+import android.util.Size;
 
 import libcore.util.HexEncoding;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -86,6 +74,7 @@
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -160,44 +149,34 @@
     /** {@hide} */
     public static final String SCAN_VOLUME_CALL = "scan_volume";
     /** {@hide} */
-    public static final String SUICIDE_CALL = "suicide";
+    public static final String CREATE_WRITE_REQUEST_CALL = "create_write_request";
+    /** {@hide} */
+    public static final String CREATE_TRASH_REQUEST_CALL = "create_trash_request";
+    /** {@hide} */
+    public static final String CREATE_FAVORITE_REQUEST_CALL = "create_favorite_request";
+    /** {@hide} */
+    public static final String CREATE_DELETE_REQUEST_CALL = "create_delete_request";
 
-    /**
-     * Extra used with {@link #SCAN_FILE_CALL} or {@link #SCAN_VOLUME_CALL} to indicate that
-     * the file path originated from shell.
-     *
-     * {@hide}
-     */
-    public static final String EXTRA_ORIGINATED_FROM_SHELL =
-            "android.intent.extra.originated_from_shell";
-
-    /**
-     * The method name used by the media scanner and mtp to tell the media provider to
-     * rescan and reclassify that have become unhidden because of renaming folders or
-     * removing nomedia files
-     * @hide
-     */
-    @Deprecated
-    public static final String UNHIDE_CALL = "unhide";
-
-    /**
-     * The method name used by the media scanner service to reload all localized ringtone titles due
-     * to a locale change.
-     * @hide
-     */
-    public static final String RETRANSLATE_CALL = "update_titles";
 
     /** {@hide} */
     public static final String GET_VERSION_CALL = "get_version";
+
     /** {@hide} */
     public static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
     /** {@hide} */
     public static final String GET_MEDIA_URI_CALL = "get_media_uri";
 
     /** {@hide} */
-    public static final String GET_CONTRIBUTED_MEDIA_CALL = "get_contributed_media";
+    public static final String EXTRA_URI = "uri";
     /** {@hide} */
-    public static final String DELETE_CONTRIBUTED_MEDIA_CALL = "delete_contributed_media";
+    public static final String EXTRA_URI_PERMISSIONS = "uriPermissions";
+
+    /** {@hide} */
+    public static final String EXTRA_CLIP_DATA = "clip_data";
+    /** {@hide} */
+    public static final String EXTRA_CONTENT_VALUES = "content_values";
+    /** {@hide} */
+    public static final String EXTRA_RESULT = "result";
 
     /**
      * This is for internal use by the media scanner only.
@@ -373,10 +352,10 @@
      * service.
      * <p>
      * This meta-data should reference the fully qualified class name of the prewarm service
-     * extending {@link CameraPrewarmService}.
+     * extending {@code CameraPrewarmService}.
      * <p>
      * The prewarm service will get bound and receive a prewarm signal
-     * {@link CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
+     * {@code CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
      * An application implementing a prewarm service should do the absolute minimum amount of work
      * to initialize the camera in order to reduce startup time in likely case that shortly after a
      * camera launch intent would be sent.
@@ -606,6 +585,10 @@
      * {@link ContentResolver#delete}.
      * <p>
      * By default, trashed items are filtered away from operations.
+     *
+     * @see MediaColumns#IS_TRASHED
+     * @see MediaStore#QUERY_ARG_MATCH_TRASHED
+     * @see MediaStore#createTrashRequest
      */
     @Match
     public static final String QUERY_ARG_MATCH_TRASHED = "android:query-arg-match-trashed";
@@ -620,6 +603,10 @@
      * <p>
      * By default, favorite items are <em>not</em> filtered away from
      * operations.
+     *
+     * @see MediaColumns#IS_FAVORITE
+     * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+     * @see MediaStore#createFavoriteRequest
      */
     @Match
     public static final String QUERY_ARG_MATCH_FAVORITE = "android:query-arg-match-favorite";
@@ -749,197 +736,6 @@
     }
 
     /**
-     * Create a new pending media item using the given parameters. Pending items
-     * are expected to have a short lifetime, and owners should either
-     * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
-     * pending item within a few hours after first creating it.
-     *
-     * @return token which can be passed to {@link #openPending(Context, Uri)}
-     *         to work with this pending item.
-     * @see MediaColumns#IS_PENDING
-     * @see MediaStore#setIncludePending(Uri)
-     * @see MediaStore#createPending(Context, PendingParams)
-     * @removed
-     */
-    @Deprecated
-    public static @NonNull Uri createPending(@NonNull Context context,
-            @NonNull PendingParams params) {
-        return context.getContentResolver().insert(params.insertUri, params.insertValues);
-    }
-
-    /**
-     * Open a pending media item to make progress on it. You can open a pending
-     * item multiple times before finally calling either
-     * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
-     *
-     * @param uri token which was previously returned from
-     *            {@link #createPending(Context, PendingParams)}.
-     * @removed
-     */
-    @Deprecated
-    public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
-        return new PendingSession(context, uri);
-    }
-
-    /**
-     * Parameters that describe a pending media item.
-     *
-     * @removed
-     */
-    @Deprecated
-    public static class PendingParams {
-        /** {@hide} */
-        public final Uri insertUri;
-        /** {@hide} */
-        public final ContentValues insertValues;
-
-        /**
-         * Create parameters that describe a pending media item.
-         *
-         * @param insertUri the {@code content://} Uri where this pending item
-         *            should be inserted when finally published. For example, to
-         *            publish an image, use
-         *            {@link MediaStore.Images.Media#getContentUri(String)}.
-         */
-        public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
-                @NonNull String mimeType) {
-            this.insertUri = Objects.requireNonNull(insertUri);
-            final long now = System.currentTimeMillis() / 1000;
-            this.insertValues = new ContentValues();
-            this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
-            this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
-            this.insertValues.put(MediaColumns.DATE_ADDED, now);
-            this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
-            this.insertValues.put(MediaColumns.IS_PENDING, 1);
-            this.insertValues.put(MediaColumns.DATE_EXPIRES,
-                    (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
-        }
-
-        public void setRelativePath(@Nullable String relativePath) {
-            if (relativePath == null) {
-                this.insertValues.remove(MediaColumns.RELATIVE_PATH);
-            } else {
-                this.insertValues.put(MediaColumns.RELATIVE_PATH, relativePath);
-            }
-        }
-
-        /**
-         * Optionally set the Uri from where the file has been downloaded. This is used
-         * for files being added to {@link Downloads} table.
-         *
-         * @see DownloadColumns#DOWNLOAD_URI
-         */
-        public void setDownloadUri(@Nullable Uri downloadUri) {
-            if (downloadUri == null) {
-                this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
-            } else {
-                this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
-            }
-        }
-
-        /**
-         * Optionally set the Uri indicating HTTP referer of the file. This is used for
-         * files being added to {@link Downloads} table.
-         *
-         * @see DownloadColumns#REFERER_URI
-         */
-        public void setRefererUri(@Nullable Uri refererUri) {
-            if (refererUri == null) {
-                this.insertValues.remove(DownloadColumns.REFERER_URI);
-            } else {
-                this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
-            }
-        }
-    }
-
-    /**
-     * Session actively working on a pending media item. Pending items are
-     * expected to have a short lifetime, and owners should either
-     * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
-     * pending item within a few hours after first creating it.
-     *
-     * @removed
-     */
-    @Deprecated
-    public static class PendingSession implements AutoCloseable {
-        /** {@hide} */
-        private final Context mContext;
-        /** {@hide} */
-        private final Uri mUri;
-
-        /** {@hide} */
-        public PendingSession(Context context, Uri uri) {
-            mContext = Objects.requireNonNull(context);
-            mUri = Objects.requireNonNull(uri);
-        }
-
-        /**
-         * Open the underlying file representing this media item. When a media
-         * item is successfully completed, you should
-         * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
-         *
-         * @see #notifyProgress(int)
-         */
-        public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
-            return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
-        }
-
-        /**
-         * Open the underlying file representing this media item. When a media
-         * item is successfully completed, you should
-         * {@link OutputStream#close()} and then {@link #publish()} it.
-         *
-         * @see #notifyProgress(int)
-         */
-        public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
-            return mContext.getContentResolver().openOutputStream(mUri);
-        }
-
-        /**
-         * Notify of current progress on this pending media item. Gallery
-         * applications may choose to surface progress information of this
-         * pending item.
-         *
-         * @param progress a percentage between 0 and 100.
-         */
-        public void notifyProgress(@IntRange(from = 0, to = 100) int progress) {
-            final Uri withProgress = mUri.buildUpon()
-                    .appendQueryParameter(PARAM_PROGRESS, Integer.toString(progress)).build();
-            mContext.getContentResolver().notifyChange(withProgress, null, 0);
-        }
-
-        /**
-         * When this media item is successfully completed, call this method to
-         * publish and make the final item visible to the user.
-         *
-         * @return the final {@code content://} Uri representing the newly
-         *         published media.
-         */
-        public @NonNull Uri publish() {
-            final ContentValues values = new ContentValues();
-            values.put(MediaColumns.IS_PENDING, 0);
-            values.putNull(MediaColumns.DATE_EXPIRES);
-            mContext.getContentResolver().update(mUri, values, null, null);
-            return mUri;
-        }
-
-        /**
-         * When this media item has failed to be completed, call this method to
-         * destroy the pending item record and any data related to it.
-         */
-        public void abandon() {
-            mContext.getContentResolver().delete(mUri, null, null);
-        }
-
-        @Override
-        public void close() {
-            // No resources to close, but at least we can inform people that no
-            // progress is being actively made.
-            notifyProgress(-1);
-        }
-    }
-
-    /**
      * Mark the given item as being "trashed", meaning it should be deleted at
      * some point in the future. This is a more gentle operation than simply
      * calling {@link ContentResolver#delete(Uri, String, String[])}, which
@@ -952,7 +748,9 @@
      * @see MediaStore#setIncludeTrashed(Uri)
      * @see MediaStore#trash(Context, Uri)
      * @see MediaStore#untrash(Context, Uri)
+     * @removed
      */
+    @Deprecated
     public static void trash(@NonNull Context context, @NonNull Uri uri) {
         trash(context, uri, 48 * DateUtils.HOUR_IN_MILLIS);
     }
@@ -970,7 +768,9 @@
      * @see MediaStore#setIncludeTrashed(Uri)
      * @see MediaStore#trash(Context, Uri)
      * @see MediaStore#untrash(Context, Uri)
+     * @removed
      */
+    @Deprecated
     public static void trash(@NonNull Context context, @NonNull Uri uri,
             @DurationMillisLong long timeoutMillis) {
         if (timeoutMillis < 0) {
@@ -992,7 +792,9 @@
      * @see MediaStore#setIncludeTrashed(Uri)
      * @see MediaStore#trash(Context, Uri)
      * @see MediaStore#untrash(Context, Uri)
+     * @removed
      */
+    @Deprecated
     public static void untrash(@NonNull Context context, @NonNull Uri uri) {
         final ContentValues values = new ContentValues();
         values.put(MediaColumns.IS_TRASHED, 0);
@@ -1010,6 +812,180 @@
         return uri.buildUpon().authority(MediaStore.AUTHORITY_LEGACY).build();
     }
 
+    private static @NonNull PendingIntent createRequest(@NonNull ContentResolver resolver,
+            @NonNull String method, @NonNull Collection<Uri> uris, @Nullable ContentValues values) {
+        Objects.requireNonNull(resolver);
+        Objects.requireNonNull(uris);
+
+        final Iterator<Uri> it = uris.iterator();
+        final ClipData clipData = ClipData.newRawUri(null, it.next());
+        while (it.hasNext()) {
+            clipData.addItem(new ClipData.Item(it.next()));
+        }
+
+        final Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_CLIP_DATA, clipData);
+        extras.putParcelable(EXTRA_CONTENT_VALUES, values);
+        return resolver.call(AUTHORITY, method, null, extras).getParcelable(EXTRA_RESULT);
+    }
+
+    /**
+     * Create a {@link PendingIntent} that will prompt the user to grant your
+     * app write access for the requested media items.
+     * <p>
+     * This call only generates the request for a prompt; to display the prompt,
+     * call {@link Activity#startIntentSenderForResult} with
+     * {@link PendingIntent#getIntentSender()}. You can then determine if the
+     * user granted your request by testing for {@link Activity#RESULT_OK} in
+     * {@link Activity#onActivityResult}.
+     * <p>
+     * Permissions granted through this mechanism are tied to the lifecycle of
+     * the {@link Activity} that requests them. If you need to retain
+     * longer-term access for background actions, you can place items into a
+     * {@link ClipData} or {@link Intent} which can then be passed to
+     * {@link Context#startService} or
+     * {@link android.app.job.JobInfo.Builder#setClipData}. Be sure to include
+     * any relevant access modes you want to retain, such as
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     * <p>
+     * The displayed prompt will reflect all the media items you're requesting,
+     * including those for which you already hold write access. If you want to
+     * determine if you already hold write access before requesting access, use
+     * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     * <p>
+     * For security and performance reasons this method does not support
+     * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} or
+     * {@link Intent#FLAG_GRANT_PREFIX_URI_PERMISSION}.
+     *
+     * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+     *            Typically this value is {@link Context#getContentResolver()},
+     *            but if you need more explicit lifecycle controls, you can
+     *            obtain a {@link ContentProviderClient} and wrap it using
+     *            {@link ContentResolver#wrap(ContentProviderClient)}.
+     * @param uris The set of media items to include in this request. Each item
+     *            must be hosted by {@link MediaStore#AUTHORITY} and must
+     *            reference a specific media item by {@link BaseColumns#_ID}.
+     */
+    public static @NonNull PendingIntent createWriteRequest(@NonNull ContentResolver resolver,
+            @NonNull Collection<Uri> uris) {
+        return createRequest(resolver, CREATE_WRITE_REQUEST_CALL, uris, null);
+    }
+
+    /**
+     * Create a {@link PendingIntent} that will prompt the user to trash the
+     * requested media items. When the user approves this request,
+     * {@link MediaColumns#IS_TRASHED} is set on these items.
+     * <p>
+     * This call only generates the request for a prompt; to display the prompt,
+     * call {@link Activity#startIntentSenderForResult} with
+     * {@link PendingIntent#getIntentSender()}. You can then determine if the
+     * user granted your request by testing for {@link Activity#RESULT_OK} in
+     * {@link Activity#onActivityResult}.
+     * <p>
+     * The displayed prompt will reflect all the media items you're requesting,
+     * including those for which you already hold write access. If you want to
+     * determine if you already hold write access before requesting access, use
+     * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+     *            Typically this value is {@link Context#getContentResolver()},
+     *            but if you need more explicit lifecycle controls, you can
+     *            obtain a {@link ContentProviderClient} and wrap it using
+     *            {@link ContentResolver#wrap(ContentProviderClient)}.
+     * @param uris The set of media items to include in this request. Each item
+     *            must be hosted by {@link MediaStore#AUTHORITY} and must
+     *            reference a specific media item by {@link BaseColumns#_ID}.
+     * @param value The {@link MediaColumns#IS_TRASHED} value to apply.
+     * @see MediaColumns#IS_TRASHED
+     * @see MediaStore#QUERY_ARG_MATCH_TRASHED
+     */
+    public static @NonNull PendingIntent createTrashRequest(@NonNull ContentResolver resolver,
+            @NonNull Collection<Uri> uris, boolean value) {
+        final ContentValues values = new ContentValues();
+        if (value) {
+            values.put(MediaColumns.IS_TRASHED, 1);
+            values.put(MediaColumns.DATE_EXPIRES,
+                    (System.currentTimeMillis() + DateUtils.WEEK_IN_MILLIS) / 1000);
+        } else {
+            values.put(MediaColumns.IS_TRASHED, 0);
+            values.putNull(MediaColumns.DATE_EXPIRES);
+        }
+        return createRequest(resolver, CREATE_TRASH_REQUEST_CALL, uris, values);
+    }
+
+    /**
+     * Create a {@link PendingIntent} that will prompt the user to favorite the
+     * requested media items. When the user approves this request,
+     * {@link MediaColumns#IS_FAVORITE} is set on these items.
+     * <p>
+     * This call only generates the request for a prompt; to display the prompt,
+     * call {@link Activity#startIntentSenderForResult} with
+     * {@link PendingIntent#getIntentSender()}. You can then determine if the
+     * user granted your request by testing for {@link Activity#RESULT_OK} in
+     * {@link Activity#onActivityResult}.
+     * <p>
+     * The displayed prompt will reflect all the media items you're requesting,
+     * including those for which you already hold write access. If you want to
+     * determine if you already hold write access before requesting access, use
+     * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+     *            Typically this value is {@link Context#getContentResolver()},
+     *            but if you need more explicit lifecycle controls, you can
+     *            obtain a {@link ContentProviderClient} and wrap it using
+     *            {@link ContentResolver#wrap(ContentProviderClient)}.
+     * @param uris The set of media items to include in this request. Each item
+     *            must be hosted by {@link MediaStore#AUTHORITY} and must
+     *            reference a specific media item by {@link BaseColumns#_ID}.
+     * @param value The {@link MediaColumns#IS_FAVORITE} value to apply.
+     * @see MediaColumns#IS_FAVORITE
+     * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+     */
+    public static @NonNull PendingIntent createFavoriteRequest(@NonNull ContentResolver resolver,
+            @NonNull Collection<Uri> uris, boolean value) {
+        final ContentValues values = new ContentValues();
+        if (value) {
+            values.put(MediaColumns.IS_FAVORITE, 1);
+        } else {
+            values.put(MediaColumns.IS_FAVORITE, 0);
+        }
+        return createRequest(resolver, CREATE_FAVORITE_REQUEST_CALL, uris, values);
+    }
+
+    /**
+     * Create a {@link PendingIntent} that will prompt the user to permanently
+     * delete the requested media items. When the user approves this request,
+     * {@link ContentResolver#delete} will be called on these items.
+     * <p>
+     * This call only generates the request for a prompt; to display the prompt,
+     * call {@link Activity#startIntentSenderForResult} with
+     * {@link PendingIntent#getIntentSender()}. You can then determine if the
+     * user granted your request by testing for {@link Activity#RESULT_OK} in
+     * {@link Activity#onActivityResult}.
+     * <p>
+     * The displayed prompt will reflect all the media items you're requesting,
+     * including those for which you already hold write access. If you want to
+     * determine if you already hold write access before requesting access, use
+     * {@code ContentResolver#checkUriPermission(Uri, int, int)} with
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
+     *
+     * @param resolver Used to connect with {@link MediaStore#AUTHORITY}.
+     *            Typically this value is {@link Context#getContentResolver()},
+     *            but if you need more explicit lifecycle controls, you can
+     *            obtain a {@link ContentProviderClient} and wrap it using
+     *            {@link ContentResolver#wrap(ContentProviderClient)}.
+     * @param uris The set of media items to include in this request. Each item
+     *            must be hosted by {@link MediaStore#AUTHORITY} and must
+     *            reference a specific media item by {@link BaseColumns#_ID}.
+     */
+    public static @NonNull PendingIntent createDeleteRequest(@NonNull ContentResolver resolver,
+            @NonNull Collection<Uri> uris) {
+        return createRequest(resolver, CREATE_DELETE_REQUEST_CALL, uris, null);
+    }
+
     /**
      * Common media metadata columns.
      */
@@ -1127,9 +1103,9 @@
          * Trashed items are retained until they expire as defined by
          * {@link #DATE_EXPIRES}.
          *
+         * @see MediaColumns#IS_TRASHED
          * @see MediaStore#QUERY_ARG_MATCH_TRASHED
-         * @see MediaStore#trash(Context, Uri)
-         * @see MediaStore#untrash(Context, Uri)
+         * @see MediaStore#createTrashRequest
          */
         @Column(Cursor.FIELD_TYPE_INTEGER)
         public static final String IS_TRASHED = "is_trashed";
@@ -1302,7 +1278,9 @@
          * Flag indicating if the media item has been marked as being a
          * "favorite" by the user.
          *
+         * @see MediaColumns#IS_FAVORITE
          * @see MediaStore#QUERY_ARG_MATCH_FAVORITE
+         * @see MediaStore#createFavoriteRequest
          */
         @Column(Cursor.FIELD_TYPE_INTEGER)
         public static final String IS_FAVORITE = "is_favorite";
@@ -1493,34 +1471,22 @@
             return ContentUris.withAppendedId(getContentUri(volumeName), rowId);
         }
 
-        /**
-         * For use only by the MTP implementation.
-         * @hide
-         */
+        /** {@hide} */
         @UnsupportedAppUsage
-        public static Uri getMtpObjectsUri(String volumeName) {
-            return AUTHORITY_URI.buildUpon().appendPath(volumeName).appendPath("object").build();
+        public static Uri getMtpObjectsUri(@NonNull String volumeName) {
+            return MediaStore.Files.getContentUri(volumeName);
         }
 
-        /**
-         * For use only by the MTP implementation.
-         * @hide
-         */
+        /** {@hide} */
         @UnsupportedAppUsage
-        public static final Uri getMtpObjectsUri(String volumeName,
-                long fileId) {
-            return ContentUris.withAppendedId(getMtpObjectsUri(volumeName), fileId);
+        public static final Uri getMtpObjectsUri(@NonNull String volumeName, long fileId) {
+            return MediaStore.Files.getContentUri(volumeName, fileId);
         }
 
-        /**
-         * Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
-         * @hide
-         */
+        /** {@hide} */
         @UnsupportedAppUsage
-        public static final Uri getMtpReferencesUri(String volumeName,
-                long fileId) {
-            return getMtpObjectsUri(volumeName, fileId).buildUpon().appendPath("references")
-                    .build();
+        public static final Uri getMtpReferencesUri(@NonNull String volumeName, long fileId) {
+            return MediaStore.Files.getContentUri(volumeName, fileId);
         }
 
         /**
@@ -1643,9 +1609,21 @@
         public static final int FULL_SCREEN_KIND = 2;
         public static final int MICRO_KIND = 3;
 
-        public static final Point MINI_SIZE = new Point(512, 384);
-        public static final Point FULL_SCREEN_SIZE = new Point(1024, 786);
-        public static final Point MICRO_SIZE = new Point(96, 96);
+        public static final Size MINI_SIZE = new Size(512, 384);
+        public static final Size FULL_SCREEN_SIZE = new Size(1024, 786);
+        public static final Size MICRO_SIZE = new Size(96, 96);
+
+        public static @NonNull Size getKindSize(int kind) {
+            if (kind == ThumbnailConstants.MICRO_KIND) {
+                return ThumbnailConstants.MICRO_SIZE;
+            } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
+                return ThumbnailConstants.FULL_SCREEN_SIZE;
+            } else if (kind == ThumbnailConstants.MINI_KIND) {
+                return ThumbnailConstants.MINI_SIZE;
+            } else {
+                throw new IllegalArgumentException("Unsupported kind: " + kind);
+            }
+        }
     }
 
     /**
@@ -1749,22 +1727,22 @@
         }
     }
 
-    /** {@hide} */
+    /**
+     * @deprecated since this method doesn't have a {@link Context}, we can't
+     *             find the actual {@link StorageVolume} for the given path, so
+     *             only a vague guess is returned. Callers should use
+     *             {@link StorageManager#getStorageVolume(File)} instead.
+     * @hide
+     */
+    @Deprecated
     public static @NonNull String getVolumeName(@NonNull File path) {
-        if (FileUtils.contains(Environment.getStorageDirectory(), path)) {
-            final StorageManager sm = AppGlobals.getInitialApplication()
-                    .getSystemService(StorageManager.class);
-            final StorageVolume sv = sm.getStorageVolume(path);
-            if (sv != null) {
-                if (sv.isPrimary()) {
-                    return VOLUME_EXTERNAL_PRIMARY;
-                } else {
-                    return checkArgumentVolumeName(sv.getNormalizedUuid());
-                }
-            }
-            throw new IllegalStateException("Unknown volume at " + path);
+        // Ideally we'd find the relevant StorageVolume, but we don't have a
+        // Context to obtain it from, so the best we can do is assume
+        if (path.getAbsolutePath()
+                .startsWith(Environment.getStorageDirectory().getAbsolutePath())) {
+            return MediaStore.VOLUME_EXTERNAL;
         } else {
-            return VOLUME_INTERNAL;
+            return MediaStore.VOLUME_INTERNAL;
         }
     }
 
@@ -1777,7 +1755,7 @@
         /**
          * Currently outstanding thumbnail requests that can be cancelled.
          */
-        @GuardedBy("sPending")
+        // @GuardedBy("sPending")
         private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>();
 
         /**
@@ -1789,16 +1767,7 @@
         @Deprecated
         static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri,
                 int kind, @Nullable BitmapFactory.Options opts) {
-            final Point size;
-            if (kind == ThumbnailConstants.MICRO_KIND) {
-                size = ThumbnailConstants.MICRO_SIZE;
-            } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
-                size = ThumbnailConstants.FULL_SCREEN_SIZE;
-            } else if (kind == ThumbnailConstants.MINI_KIND) {
-                size = ThumbnailConstants.MINI_SIZE;
-            } else {
-                throw new IllegalArgumentException("Unsupported kind: " + kind);
-            }
+            final Size size = ThumbnailConstants.getKindSize(kind);
 
             CancellationSignal signal = null;
             synchronized (sPending) {
@@ -1810,7 +1779,7 @@
             }
 
             try {
-                return cr.loadThumbnail(uri, Point.convert(size), signal);
+                return cr.loadThumbnail(uri, size, signal);
             } catch (IOException e) {
                 Log.w(TAG, "Failed to obtain thumbnail for " + uri, e);
                 return null;
@@ -2007,26 +1976,14 @@
             @Deprecated
             public static final String insertImage(ContentResolver cr, String imagePath,
                     String name, String description) throws FileNotFoundException {
-                final File file = new File(imagePath);
-                final String mimeType = MediaFile.getMimeTypeForFile(imagePath);
-
-                if (TextUtils.isEmpty(name)) name = "Image";
-                final PendingParams params = new PendingParams(
-                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, name, mimeType);
-
-                final Context context = AppGlobals.getInitialApplication();
-                final Uri pendingUri = createPending(context, params);
-                try (PendingSession session = openPending(context, pendingUri)) {
-                    try (InputStream in = new FileInputStream(file);
-                         OutputStream out = session.openOutputStream()) {
-                        FileUtils.copy(in, out);
-                    }
-                    return session.publish().toString();
-                } catch (Exception e) {
-                    Log.w(TAG, "Failed to insert image", e);
-                    context.getContentResolver().delete(pendingUri, null, null);
-                    return null;
+                final Bitmap source;
+                try {
+                    source = ImageDecoder
+                            .decodeBitmap(ImageDecoder.createSource(new File(imagePath)));
+                } catch (IOException e) {
+                    throw new FileNotFoundException(e.getMessage());
                 }
+                return insertImage(cr, source, name, description);
             }
 
             /**
@@ -2043,22 +2000,34 @@
              *             control over lifecycle.
              */
             @Deprecated
-            public static final String insertImage(ContentResolver cr, Bitmap source,
-                                                   String title, String description) {
+            public static final String insertImage(ContentResolver cr, Bitmap source, String title,
+                    String description) {
                 if (TextUtils.isEmpty(title)) title = "Image";
-                final PendingParams params = new PendingParams(
-                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, title, "image/jpeg");
 
-                final Context context = AppGlobals.getInitialApplication();
-                final Uri pendingUri = createPending(context, params);
-                try (PendingSession session = openPending(context, pendingUri)) {
-                    try (OutputStream out = session.openOutputStream()) {
+                final long now = System.currentTimeMillis();
+                final ContentValues values = new ContentValues();
+                values.put(MediaColumns.DISPLAY_NAME, title);
+                values.put(MediaColumns.MIME_TYPE, "image/jpeg");
+                values.put(MediaColumns.DATE_ADDED, now / 1000);
+                values.put(MediaColumns.DATE_MODIFIED, now / 1000);
+                values.put(MediaColumns.DATE_EXPIRES, (now + DateUtils.DAY_IN_MILLIS) / 1000);
+                values.put(MediaColumns.IS_PENDING, 1);
+
+                final Uri uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+                try {
+                    try (OutputStream out = cr.openOutputStream(uri)) {
                         source.compress(Bitmap.CompressFormat.JPEG, 90, out);
                     }
-                    return session.publish().toString();
+
+                    // Everything went well above, publish it!
+                    values.clear();
+                    values.put(MediaColumns.IS_PENDING, 0);
+                    values.putNull(MediaColumns.DATE_EXPIRES);
+                    cr.update(uri, values, null, null);
+                    return uri.toString();
                 } catch (Exception e) {
                     Log.w(TAG, "Failed to insert image", e);
-                    context.getContentResolver().delete(pendingUri, null, null);
+                    cr.delete(uri, null, null);
                     return null;
                 }
             }
@@ -2318,6 +2287,14 @@
             public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
 
             /**
+             * Return the typical {@link Size} (in pixels) used internally when
+             * the given thumbnail kind is requested.
+             */
+            public static @NonNull Size getKindSize(int kind) {
+                return ThumbnailConstants.getKindSize(kind);
+            }
+
+            /**
              * The blob raw data of thumbnail
              *
              * @deprecated this column never existed internally, and could never
@@ -3572,6 +3549,14 @@
             public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND;
 
             /**
+             * Return the typical {@link Size} (in pixels) used internally when
+             * the given thumbnail kind is requested.
+             */
+            public static @NonNull Size getKindSize(int kind) {
+                return ThumbnailConstants.getKindSize(kind);
+            }
+
+            /**
              * The width of the thumbnal
              */
             @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
@@ -3603,54 +3588,44 @@
      */
     public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) {
         final StorageManager sm = context.getSystemService(StorageManager.class);
-        final Set<String> volumeNames = new ArraySet<>();
-        for (VolumeInfo vi : sm.getVolumes()) {
-            if (vi.isVisibleForUser(UserHandle.myUserId()) && vi.isMountedReadable()) {
-                if (vi.isPrimary()) {
-                    volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
-                } else {
-                    volumeNames.add(vi.getNormalizedFsUuid());
+        final Set<String> res = new ArraySet<>();
+        for (StorageVolume sv : sm.getStorageVolumes()) {
+            switch (sv.getState()) {
+                case Environment.MEDIA_MOUNTED:
+                case Environment.MEDIA_MOUNTED_READ_ONLY: {
+                    final String volumeName = sv.getMediaStoreVolumeName();
+                    if (volumeName != null) {
+                        res.add(volumeName);
+                    }
+                    break;
                 }
             }
         }
-        return volumeNames;
+        return res;
     }
 
     /**
-     * Return list of all specific volume names that have recently been part of
+     * Return list of all recent volume names that have been part of
      * {@link #VOLUME_EXTERNAL}.
      * <p>
-     * This includes both currently mounted volumes <em>and</em> recently
-     * mounted (but currently unmounted) volumes. Any indexed metadata for these
-     * volumes is preserved to optimize the speed of remounting at a later time.
-     *
-     * @hide
+     * These volume names are not currently mounted, but they're likely to
+     * reappear in the future, so apps are encouraged to preserve any indexed
+     * metadata related to these volumes to optimize user experiences.
+     * <p>
+     * Each specific volume name can be passed to APIs like
+     * {@link MediaStore.Images.Media#getContentUri(String)} to interact with
+     * media on that storage device.
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
     public static @NonNull Set<String> getRecentExternalVolumeNames(@NonNull Context context) {
         final StorageManager sm = context.getSystemService(StorageManager.class);
-
-        // We always have primary storage
-        final Set<String> volumeNames = new ArraySet<>();
-        volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
-
-        final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
-        for (VolumeRecord rec : sm.getVolumeRecords()) {
-            // Skip volumes without valid UUIDs
-            if (TextUtils.isEmpty(rec.fsUuid)) continue;
-
-            final VolumeInfo vi = sm.findVolumeByUuid(rec.fsUuid);
-            if (vi != null && vi.isVisibleForUser(UserHandle.myUserId())
-                    && vi.isMountedReadable()) {
-                // We're mounted right now
-                volumeNames.add(rec.getNormalizedFsUuid());
-            } else if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
-                // We're not mounted right now, but we've been seen recently
-                volumeNames.add(rec.getNormalizedFsUuid());
+        final Set<String> res = new ArraySet<>();
+        for (StorageVolume sv : sm.getRecentStorageVolumes()) {
+            final String volumeName = sv.getMediaStoreVolumeName();
+            if (volumeName != null) {
+                res.add(volumeName);
             }
         }
-        return volumeNames;
+        return res;
     }
 
     /**
@@ -3703,97 +3678,6 @@
     }
 
     /**
-     * Return path where the given specific volume is mounted. Not valid for
-     * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are
-     * broad collections that cover many paths.
-     *
-     * @hide
-     */
-    @TestApi
-    public static @NonNull File getVolumePath(@NonNull String volumeName)
-            throws FileNotFoundException {
-        final StorageManager sm = AppGlobals.getInitialApplication()
-                .getSystemService(StorageManager.class);
-        return getVolumePath(sm.getVolumes(), volumeName);
-    }
-
-    /** {@hide} */
-    public static @NonNull File getVolumePath(@NonNull List<VolumeInfo> volumes,
-            @NonNull String volumeName) throws FileNotFoundException {
-        if (TextUtils.isEmpty(volumeName)) {
-            throw new IllegalArgumentException();
-        }
-
-        switch (volumeName) {
-            case VOLUME_INTERNAL:
-            case VOLUME_EXTERNAL:
-                throw new FileNotFoundException(volumeName + " has no associated path");
-        }
-
-        final boolean wantPrimary = VOLUME_EXTERNAL_PRIMARY.equals(volumeName);
-        for (VolumeInfo volume : volumes) {
-            final boolean matchPrimary = wantPrimary
-                    && volume.isPrimary();
-            final boolean matchSecondary = !wantPrimary
-                    && Objects.equals(volume.getNormalizedFsUuid(), volumeName);
-            if (matchPrimary || matchSecondary) {
-                final File path = volume.getPathForUser(UserHandle.myUserId());
-                if (path != null) {
-                    return path;
-                }
-            }
-        }
-        throw new FileNotFoundException("Failed to find path for " + volumeName);
-    }
-
-    /**
-     * Return paths that should be scanned for the given volume.
-     *
-     * @hide
-     */
-    @TestApi
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
-    public static @NonNull Collection<File> getVolumeScanPaths(@NonNull String volumeName)
-            throws FileNotFoundException {
-        if (TextUtils.isEmpty(volumeName)) {
-            throw new IllegalArgumentException();
-        }
-
-        final Context context = AppGlobals.getInitialApplication();
-        final UserManager um = context.getSystemService(UserManager.class);
-
-        final ArrayList<File> res = new ArrayList<>();
-        if (VOLUME_INTERNAL.equals(volumeName)) {
-            addCanonicalFile(res, new File(Environment.getRootDirectory(), "media"));
-            addCanonicalFile(res, new File(Environment.getOemDirectory(), "media"));
-            addCanonicalFile(res, new File(Environment.getProductDirectory(), "media"));
-        } else if (VOLUME_EXTERNAL.equals(volumeName)) {
-            for (String exactVolume : getExternalVolumeNames(context)) {
-                addCanonicalFile(res, getVolumePath(exactVolume));
-            }
-            if (um.isDemoUser()) {
-                addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
-            }
-        } else {
-            addCanonicalFile(res, getVolumePath(volumeName));
-            if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName) && um.isDemoUser()) {
-                addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
-            }
-        }
-        return res;
-    }
-
-    private static void addCanonicalFile(List<File> list, File file) {
-        try {
-            list.add(file.getCanonicalFile());
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to resolve " + file + ": " + e);
-            list.add(file);
-        }
-    }
-
-    /**
      * Uri for querying the state of the media scanner.
      */
     public static Uri getMediaScannerUri() {
@@ -3876,10 +3760,10 @@
 
         try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
             final Bundle in = new Bundle();
-            in.putParcelable(DocumentsContract.EXTRA_URI, mediaUri);
-            in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+            in.putParcelable(EXTRA_URI, mediaUri);
+            in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions));
             final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in);
-            return out.getParcelable(DocumentsContract.EXTRA_URI);
+            return out.getParcelable(EXTRA_URI);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
@@ -3906,134 +3790,43 @@
 
         try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
             final Bundle in = new Bundle();
-            in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
-            in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
+            in.putParcelable(EXTRA_URI, documentUri);
+            in.putParcelableArrayList(EXTRA_URI_PERMISSIONS, new ArrayList<>(uriPermissions));
             final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in);
-            return out.getParcelable(DocumentsContract.EXTRA_URI);
+            return out.getParcelable(EXTRA_URI);
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
     }
 
+    /** @hide */
+    @TestApi
+    public static void waitForIdle(@NonNull ContentResolver resolver) {
+        resolver.call(AUTHORITY, WAIT_FOR_IDLE_CALL, null, null);
+    }
+
     /**
-     * Calculate size of media contributed by given package under the calling
-     * user. The meaning of "contributed" means it won't automatically be
-     * deleted when the app is uninstalled.
+     * Perform a blocking scan of the given {@link File}, returning the
+     * {@link Uri} of the scanned file.
      *
      * @hide
      */
+    @SystemApi
     @TestApi
-    @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
-    public static @BytesLong long getContributedMediaSize(Context context, String packageName,
-            UserHandle user) throws IOException {
-        final UserManager um = context.getSystemService(UserManager.class);
-        if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
-            try {
-                final ContentResolver resolver = context
-                        .createPackageContextAsUser(packageName, 0, user).getContentResolver();
-                final Bundle in = new Bundle();
-                in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
-                final Bundle out = resolver.call(AUTHORITY, GET_CONTRIBUTED_MEDIA_CALL, null, in);
-                return out.getLong(Intent.EXTRA_INDEX);
-            } catch (Exception e) {
-                throw new IOException(e);
-            }
-        } else {
-            throw new IOException("User " + user + " must be unlocked and running");
-        }
+    @SuppressLint("StreamFiles")
+    public static @NonNull Uri scanFile(@NonNull ContentResolver resolver, @NonNull File file) {
+        final Bundle out = resolver.call(AUTHORITY, SCAN_FILE_CALL, file.getAbsolutePath(), null);
+        return out.getParcelable(Intent.EXTRA_STREAM);
     }
 
     /**
-     * Delete all media contributed by given package under the calling user. The
-     * meaning of "contributed" means it won't automatically be deleted when the
-     * app is uninstalled.
+     * Perform a blocking scan of the given storage volume.
      *
      * @hide
      */
+    @SystemApi
     @TestApi
-    @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA)
-    public static void deleteContributedMedia(Context context, String packageName,
-            UserHandle user) throws IOException {
-        final UserManager um = context.getSystemService(UserManager.class);
-        if (um.isUserUnlocked(user) && um.isUserRunning(user)) {
-            try {
-                final ContentResolver resolver = context
-                        .createPackageContextAsUser(packageName, 0, user).getContentResolver();
-                final Bundle in = new Bundle();
-                in.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
-                resolver.call(AUTHORITY, DELETE_CONTRIBUTED_MEDIA_CALL, null, in);
-            } catch (Exception e) {
-                throw new IOException(e);
-            }
-        } else {
-            throw new IOException("User " + user + " must be unlocked and running");
-        }
-    }
-
-    /** @hide */
-    @TestApi
-    public static void waitForIdle(Context context) {
-        final ContentResolver resolver = context.getContentResolver();
-        try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
-            client.call(WAIT_FOR_IDLE_CALL, null, null);
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    /** @hide */
-    public static void suicide(Context context) {
-        final ContentResolver resolver = context.getContentResolver();
-        try (ContentProviderClient client = resolver
-                .acquireUnstableContentProviderClient(AUTHORITY)) {
-            client.call(SUICIDE_CALL, null, null);
-        } catch (Exception ignored) {
-        }
-    }
-
-    /** @hide */
-    @TestApi
-    public static Uri scanFile(Context context, File file) {
-        return scan(context, SCAN_FILE_CALL, file, false);
-    }
-
-    /** @hide */
-    @TestApi
-    public static Uri scanFileFromShell(Context context, File file) {
-        return scan(context, SCAN_FILE_CALL, file, true);
-    }
-
-    /** @hide */
-    @TestApi
-    public static void scanVolume(Context context, File file) {
-        scan(context, SCAN_VOLUME_CALL, file, false);
-    }
-
-    /** @hide */
-    public static Uri scanFile(ContentProviderClient client, File file) {
-        return scan(client, SCAN_FILE_CALL, file, false);
-    }
-
-    /** @hide */
-    private static Uri scan(Context context, String method, File file,
-            boolean originatedFromShell) {
-        final ContentResolver resolver = context.getContentResolver();
-        try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
-            return scan(client, method, file, originatedFromShell);
-        }
-    }
-
-    /** @hide */
-    private static Uri scan(ContentProviderClient client, String method, File file,
-            boolean originatedFromShell) {
-        try {
-            final Bundle in = new Bundle();
-            in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
-            in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell);
-            final Bundle out = client.call(method, null, in);
-            return out.getParcelable(Intent.EXTRA_STREAM);
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
+    public static void scanVolume(@NonNull ContentResolver resolver, @NonNull String volumeName) {
+        resolver.call(AUTHORITY, SCAN_VOLUME_CALL, volumeName, null);
     }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bd1eb21..f4e2329 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8392,6 +8392,20 @@
                 "navigation_mode";
 
         /**
+         * Scale factor for the back gesture inset size on the left side of the screen.
+         * @hide
+         */
+        public static final String BACK_GESTURE_INSET_SCALE_LEFT =
+                "back_gesture_inset_scale_left";
+
+        /**
+         * Scale factor for the back gesture inset size on the right side of the screen.
+         * @hide
+         */
+        public static final String BACK_GESTURE_INSET_SCALE_RIGHT =
+                "back_gesture_inset_scale_right";
+
+        /**
          * Controls whether aware is enabled.
          * @hide
          */
@@ -8410,6 +8424,18 @@
         public static final String TAP_GESTURE = "tap_gesture";
 
         /**
+         * Controls whether the people strip is enabled.
+         * @hide
+         */
+        public static final String PEOPLE_STRIP = "people_strip";
+
+        /**
+         * Controls if window magnification is enabled.
+         * @hide
+         */
+        public static final String WINDOW_MAGNIFICATION = "window_magnification";
+
+        /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
          *
@@ -13627,13 +13653,6 @@
         public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader";
 
         /**
-         * Default user id to boot into. They map to user ids, for example, 10, 11, 12.
-         *
-         * @hide
-         */
-        public static final String DEFAULT_USER_ID_TO_BOOT_INTO = "default_boot_into_user_id";
-
-        /**
          * Persistent user id that is last logged in to.
          *
          * They map to user ids, for example, 10, 11, 12.
@@ -14223,6 +14242,20 @@
      */
     public static final int ADD_WIFI_RESULT_ALREADY_EXISTS = 2;
 
+    /**
+     * Activity Action: Allows user to select current bug report handler.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_BUGREPORT_HANDLER_SETTINGS =
+            "android.settings.BUGREPORT_HANDLER_SETTINGS";
+
     private static final String[] PM_WRITE_SETTINGS = {
         android.Manifest.permission.WRITE_SETTINGS
     };
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index e53ebad..72e9ad0 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -23,6 +23,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.View;
+import android.view.inputmethod.InlineSuggestionsRequest;
 
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Preconditions;
@@ -116,20 +117,34 @@
      */
     private final @RequestFlags int mFlags;
 
+    /**
+     * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+     * with this request.
+     *
+     * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
+     *
+     * @return the suggestionspec
+     */
+    private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest;
+
     private void onConstructed() {
         Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts");
     }
 
 
 
-    // Code below generated by codegen v1.0.0.
+    // Code below generated by codegen v1.0.14.
     //
     // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
     // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java
     //
-    // CHECKSTYLE:OFF Generated code
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
 
     /** @hide */
     @IntDef(flag = true, prefix = "FLAG_", value = {
@@ -184,6 +199,11 @@
      *
      *   @return any combination of {@link #FLAG_MANUAL_REQUEST} and
      *           {@link #FLAG_COMPATIBILITY_MODE_REQUEST}.
+     * @param inlineSuggestionsRequest
+     *   Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+     *   with this request.
+     *
+     *   TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
      * @hide
      */
     @DataClass.Generated.Member
@@ -191,7 +211,8 @@
             int id,
             @NonNull List<FillContext> fillContexts,
             @Nullable Bundle clientState,
-            @RequestFlags int flags) {
+            @RequestFlags int flags,
+            @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) {
         this.mId = id;
         this.mFillContexts = fillContexts;
         com.android.internal.util.AnnotationValidations.validate(
@@ -203,6 +224,7 @@
                 mFlags,
                 FLAG_MANUAL_REQUEST
                         | FLAG_COMPATIBILITY_MODE_REQUEST);
+        this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
 
         onConstructed();
     }
@@ -256,6 +278,19 @@
         return mFlags;
     }
 
+    /**
+     * Gets the {@link android.view.inputmethod.InlineSuggestionsRequest} associated
+     * with this request.
+     *
+     * TODO(b/137800469): Add more doc describing how to handle the inline suggestions request.
+     *
+     * @return the suggestionspec
+     */
+    @DataClass.Generated.Member
+    public @Nullable InlineSuggestionsRequest getInlineSuggestionsRequest() {
+        return mInlineSuggestionsRequest;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -266,29 +301,63 @@
                 "id = " + mId + ", " +
                 "fillContexts = " + mFillContexts + ", " +
                 "clientState = " + mClientState + ", " +
-                "flags = " + requestFlagsToString(mFlags) +
+                "flags = " + requestFlagsToString(mFlags) + ", " +
+                "inlineSuggestionsRequest = " + mInlineSuggestionsRequest +
         " }";
     }
 
     @Override
     @DataClass.Generated.Member
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         byte flg = 0;
         if (mClientState != null) flg |= 0x4;
+        if (mInlineSuggestionsRequest != null) flg |= 0x10;
         dest.writeByte(flg);
         dest.writeInt(mId);
         dest.writeParcelableList(mFillContexts, flags);
         if (mClientState != null) dest.writeBundle(mClientState);
         dest.writeInt(mFlags);
+        if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags);
     }
 
     @Override
     @DataClass.Generated.Member
     public int describeContents() { return 0; }
 
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ FillRequest(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        int id = in.readInt();
+        List<FillContext> fillContexts = new ArrayList<>();
+        in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
+        Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
+        int flags = in.readInt();
+        InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
+
+        this.mId = id;
+        this.mFillContexts = fillContexts;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mFillContexts);
+        this.mClientState = clientState;
+        this.mFlags = flags;
+
+        Preconditions.checkFlagsArgument(
+                mFlags,
+                FLAG_MANUAL_REQUEST
+                        | FLAG_COMPATIBILITY_MODE_REQUEST);
+        this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
+
+        onConstructed();
+    }
+
     @DataClass.Generated.Member
     public static final @NonNull Parcelable.Creator<FillRequest> CREATOR
             = new Parcelable.Creator<FillRequest>() {
@@ -298,31 +367,21 @@
         }
 
         @Override
-        @SuppressWarnings({"unchecked", "RedundantCast"})
-        public FillRequest createFromParcel(Parcel in) {
-            // You can override field unparcelling by defining methods like:
-            // static FieldType unparcelFieldName(Parcel in) { ... }
-
-            byte flg = in.readByte();
-            int id = in.readInt();
-            List<FillContext> fillContexts = new ArrayList<>();
-            in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
-            Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
-            int flags = in.readInt();
-            return new FillRequest(
-                    id,
-                    fillContexts,
-                    clientState,
-                    flags);
+        public FillRequest createFromParcel(@NonNull Parcel in) {
+            return new FillRequest(in);
         }
     };
 
     @DataClass.Generated(
-            time = 1565152134349L,
-            codegenVersion = "1.0.0",
+            time = 1575928271155L,
+            codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
-            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
     @Deprecated
     private void __metadata() {}
 
+
+    //@formatter:on
+    // End of generated code
+
 }
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index c99fe61..02a6390 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -25,6 +25,7 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.app.Activity;
+import android.app.slice.Slice;
 import android.content.IntentSender;
 import android.content.pm.ParceledListSlice;
 import android.os.Bundle;
@@ -86,6 +87,7 @@
     private int mRequestId;
     private final @Nullable UserData mUserData;
     private final @Nullable int[] mCancelIds;
+    private final @Nullable ParceledListSlice<Slice> mInlineSuggestionSlices;
 
     private FillResponse(@NonNull Builder builder) {
         mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
@@ -103,6 +105,8 @@
         mRequestId = INVALID_REQUEST_ID;
         mUserData = builder.mUserData;
         mCancelIds = builder.mCancelIds;
+        mInlineSuggestionSlices = (builder.mInlineSuggestionSlices != null)
+                ? new ParceledListSlice<>(builder.mInlineSuggestionSlices) : null;
     }
 
     /** @hide */
@@ -195,6 +199,11 @@
         return mCancelIds;
     }
 
+    /** @hide */
+    public List<Slice> getInlineSuggestionSlices() {
+        return (mInlineSuggestionSlices != null) ? mInlineSuggestionSlices.getList() : null;
+    }
+
     /**
      * Builder for {@link FillResponse} objects. You must to provide at least
      * one dataset or set an authentication intent with a presentation view.
@@ -215,6 +224,7 @@
         private boolean mDestroyed;
         private UserData mUserData;
         private int[] mCancelIds;
+        private ArrayList<Slice> mInlineSuggestionSlices;
 
         /**
          * Triggers a custom UI before before autofilling the screen with any data set in this
@@ -570,6 +580,20 @@
         }
 
         /**
+         * TODO(b/137800469): add javadoc
+         */
+        @NonNull
+        public Builder addInlineSuggestionSlice(@NonNull Slice inlineSuggestionSlice) {
+            throwIfDestroyed();
+            throwIfAuthenticationCalled();
+            if (mInlineSuggestionSlices == null) {
+                mInlineSuggestionSlices = new ArrayList<>();
+            }
+            mInlineSuggestionSlices.add(inlineSuggestionSlice);
+            return this;
+        }
+
+        /**
          * Builds a new {@link FillResponse} instance.
          *
          * @throws IllegalStateException if any of the following conditions occur:
@@ -670,7 +694,9 @@
         if (mCancelIds != null) {
             builder.append(", mCancelIds=").append(mCancelIds.length);
         }
-
+        if (mInlineSuggestionSlices != null) {
+            builder.append(", inlinedSuggestions=").append(mInlineSuggestionSlices.getList());
+        }
         return builder.append("]").toString();
     }
 
@@ -699,7 +725,7 @@
         parcel.writeParcelableArray(mFieldClassificationIds, flags);
         parcel.writeInt(mFlags);
         parcel.writeIntArray(mCancelIds);
-
+        parcel.writeParcelable(mInlineSuggestionSlices, flags);
         parcel.writeInt(mRequestId);
     }
 
@@ -755,6 +781,16 @@
             final int[] cancelIds = parcel.createIntArray();
             builder.setCancelTargetIds(cancelIds);
 
+            final ParceledListSlice<Slice> parceledInlineSuggestionSlices =
+                    parcel.readParcelable(null);
+            if (parceledInlineSuggestionSlices != null) {
+                final List<Slice> inlineSuggestionSlices = parceledInlineSuggestionSlices.getList();
+                final int size = inlineSuggestionSlices.size();
+                for (int i = 0; i < size; i++) {
+                    builder.addInlineSuggestionSlice(inlineSuggestionSlices.get(i));
+                }
+            }
+
             final FillResponse response = builder.build();
             response.setRequestId(parcel.readInt());
 
diff --git a/core/java/android/service/dreams/Sandman.java b/core/java/android/service/dreams/Sandman.java
index efb8923..f2cedbc 100644
--- a/core/java/android/service/dreams/Sandman.java
+++ b/core/java/android/service/dreams/Sandman.java
@@ -36,12 +36,6 @@
 public final class Sandman {
     private static final String TAG = "Sandman";
 
-    // The component name of a special dock app that merely launches a dream.
-    // We don't want to launch this app when docked because it causes an unnecessary
-    // activity transition.  We just want to start the dream.
-    private static final ComponentName SOMNAMBULATOR_COMPONENT =
-            new ComponentName("com.android.systemui", "com.android.systemui.Somnambulator");
-
 
     // The sandman is eternal.  No one instantiates him.
     private Sandman() {
@@ -52,8 +46,11 @@
      * False if we should dream instead, if appropriate.
      */
     public static boolean shouldStartDockApp(Context context, Intent intent) {
+        final ComponentName somnambulatorComponent = ComponentName.unflattenFromString(
+                context.getResources().getString(
+                        com.android.internal.R.string.config_somnambulatorComponent));
         ComponentName name = intent.resolveActivity(context.getPackageManager());
-        return name != null && !name.equals(SOMNAMBULATOR_COMPONENT);
+        return name != null && !name.equals(somnambulatorComponent);
     }
 
     /**
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 8ab687f..c84fbc7 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -124,6 +124,13 @@
     public static final String KEY_IMPORTANCE = "key_importance";
 
     /**
+     * Data type: float, a ranking score from 0 (lowest) to 1 (highest).
+     * Used to rank notifications inside that fall under the same classification (i.e. alerting,
+     * silenced).
+     */
+    public static final String KEY_RANKING_SCORE = "key_ranking_score";
+
+    /**
      * Create a notification adjustment.
      *
      * @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 85f13d5..c04ac59 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1895,7 +1895,8 @@
                     && ((mSmartActions == null ? 0 : mSmartActions.size())
                         == (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
                     && Objects.equals(mSmartReplies, other.mSmartReplies)
-                    && Objects.equals(mCanBubble, other.mCanBubble);
+                    && Objects.equals(mCanBubble, other.mCanBubble)
+                    && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive);
         }
     }
 
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index dd2586c..d0675ed 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -481,9 +481,12 @@
      * as true on their TileService Manifest declaration, and will do nothing otherwise.
      */
     public static final void requestListeningState(Context context, ComponentName component) {
+        final ComponentName sysuiComponent = ComponentName.unflattenFromString(
+                context.getResources().getString(
+                        com.android.internal.R.string.config_systemUIServiceComponent));
         Intent intent = new Intent(ACTION_REQUEST_LISTENING);
         intent.putExtra(Intent.EXTRA_COMPONENT_NAME, component);
-        intent.setPackage("com.android.systemui");
+        intent.setPackage(sysuiComponent.getPackageName());
         context.sendBroadcast(intent, Manifest.permission.BIND_QUICK_SETTINGS_TILE);
     }
 }
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 716a522..51a9c86 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -35,8 +35,8 @@
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 
-import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IPhoneStateListener;
 
 import dalvik.system.VMRuntime;
 
@@ -169,14 +169,6 @@
     public static final int LISTEN_SIGNAL_STRENGTHS                         = 0x00000100;
 
     /**
-     * Listen for changes to OTASP mode.
-     *
-     * @see #onOtaspChanged
-     * @hide
-     */
-    public static final int LISTEN_OTASP_CHANGED                            = 0x00000200;
-
-    /**
      * Listen for changes to observed cell info.
      *
      * @see #onCellInfoChanged
@@ -196,12 +188,13 @@
     /**
      * Listen for {@link PreciseDataConnectionState} on the data connection (cellular).
      *
-     * @see #onPreciseDataConnectionStateChanged
+     * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * or the calling app has carrier privileges
+     * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * @hide
+     * @see #onPreciseDataConnectionStateChanged
      */
-    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
-    @SystemApi
+    @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
     public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE            = 0x00001000;
 
     /**
@@ -624,29 +617,6 @@
         // default implementation empty
     }
 
-
-    /**
-     * The Over The Air Service Provisioning (OTASP) has changed on the registered subscription.
-     * Note, the registration subId comes from {@link TelephonyManager} object which registers
-     * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
-     * If this TelephonyManager object was created with
-     * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
-     * subId. Otherwise, this callback applies to
-     * {@link SubscriptionManager#getDefaultSubscriptionId()}.
-     *
-     * Requires the READ_PHONE_STATE permission.
-     * @param otaspMode is integer <code>OTASP_UNKNOWN=1<code>
-     *   means the value is currently unknown and the system should wait until
-     *   <code>OTASP_NEEDED=2<code> or <code>OTASP_NOT_NEEDED=3<code> is received before
-     *   making the decision to perform OTASP or not.
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void onOtaspChanged(int otaspMode) {
-        // default implementation empty
-    }
-
     /**
      * Callback invoked when a observed cell info has changed or new cells have been added
      * or removed on the registered subscription.
@@ -719,8 +689,9 @@
     }
 
     /**
-     * Callback invoked when data connection state changes with precise information
-     * on the registered subscription.
+     * Callback providing update about the default/internet data connection on the registered
+     * subscription.
+     *
      * Note, the registration subId comes from {@link TelephonyManager} object which registers
      * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
      * If this TelephonyManager object was created with
@@ -728,12 +699,13 @@
      * subId. Otherwise, this callback applies to
      * {@link SubscriptionManager#getDefaultSubscriptionId()}.
      *
-     * @param dataConnectionState {@link PreciseDataConnectionState}
+     * <p>Requires permission {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * or the calling app has carrier privileges
+     * (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * @hide
+     * @param dataConnectionState {@link PreciseDataConnectionState}
      */
-    @RequiresPermission((android.Manifest.permission.READ_PRECISE_PHONE_STATE))
-    @SystemApi
+    @RequiresPermission((android.Manifest.permission.MODIFY_PHONE_STATE))
     public void onPreciseDataConnectionStateChanged(
             @NonNull PreciseDataConnectionState dataConnectionState) {
         // default implementation empty
@@ -1042,11 +1014,21 @@
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
 
-            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
-                    () -> {
-                        psl.onDataConnectionStateChanged(state, networkType);
-                        psl.onDataConnectionStateChanged(state);
-                    }));
+            if (state == TelephonyManager.DATA_DISCONNECTING
+                    && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+                Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                        () -> {
+                            psl.onDataConnectionStateChanged(
+                                    TelephonyManager.DATA_CONNECTED, networkType);
+                            psl.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED);
+                        }));
+            } else {
+                Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                        () -> {
+                            psl.onDataConnectionStateChanged(state, networkType);
+                            psl.onDataConnectionStateChanged(state);
+                        }));
+            }
         }
 
         public void onDataActivity(int direction) {
@@ -1065,14 +1047,6 @@
                     () -> mExecutor.execute(() -> psl.onSignalStrengthsChanged(signalStrength)));
         }
 
-        public void onOtaspChanged(int otaspMode) {
-            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
-            if (psl == null) return;
-
-            Binder.withCleanCallingIdentity(
-                    () -> mExecutor.execute(() -> psl.onOtaspChanged(otaspMode)));
-        }
-
         public void onCellInfoChanged(List<CellInfo> cellInfo) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 9d7b57b..f574160 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -21,17 +21,13 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Context;
-import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.CallState;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.DataFailureCause;
-import android.telephony.Annotation.DataState;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.Annotation.PreciseCallStates;
 import android.telephony.Annotation.RadioPowerState;
@@ -357,27 +353,18 @@
      * @param subId for which data connection state changed.
      * @param slotIndex for which data connections state changed. Can be derived from subId except
      * when subId is invalid.
-     * @param state latest data connection state, e.g,
-     * @param isDataConnectivityPossible indicates if data is allowed
-     * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
-     * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
-     * @param linkProperties {@link LinkProperties} associated with this data connection.
-     * @param networkCapabilities {@link NetworkCapabilities} associated with this data connection.
-     * @param networkType associated with this data connection.
-     * @param roaming {@code true} indicates in roaming, {@false} otherwise.
-     * @see TelephonyManager#DATA_DISCONNECTED
-     * @see TelephonyManager#isDataConnectivityPossible()
+     * @param apnType the APN type that triggered this update
+     * @param preciseState the PreciseDataConnectionState
      *
+     * @see android.telephony.PreciseDataConnection
+     * @see TelephonyManager#DATA_DISCONNECTED
      * @hide
      */
-    public void notifyDataConnectionForSubscriber(int slotIndex, int subId, @DataState int state,
-        boolean isDataConnectivityPossible,
-        @ApnType String apn, String apnType, LinkProperties linkProperties,
-        NetworkCapabilities networkCapabilities, int networkType, boolean roaming) {
+    public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
+            String apnType, PreciseDataConnectionState preciseState) {
         try {
-            sRegistry.notifyDataConnectionForSubscriber(slotIndex, subId, state,
-                isDataConnectivityPossible,
-                apn, apnType, linkProperties, networkCapabilities, networkType, roaming);
+            sRegistry.notifyDataConnectionForSubscriber(
+                    slotIndex, subId, apnType, preciseState);
         } catch (RemoteException ex) {
             // system process is dead
         }
@@ -600,22 +587,6 @@
     }
 
     /**
-     * Notify over the air sim provisioning(OTASP) mode changed on certain subscription.
-     *
-     * @param subId for which otasp mode changed.
-     * @param otaspMode latest mode for OTASP e.g, OTASP needed.
-     *
-     * @hide
-     */
-    public void notifyOtaspChanged(int subId, int otaspMode) {
-        try {
-            sRegistry.notifyOtaspChanged(subId, otaspMode);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
-    }
-
-    /**
      * Notify precise call state changed on certain subscription, including foreground, background
      * and ringcall states.
      *
@@ -662,25 +633,6 @@
     }
 
     /**
-     * Notify data connection failed on certain subscription.
-     *
-     * @param subId for which data connection failed.
-     * @param slotIndex for which data conenction faled. Can be derived from subId except when subId
-     * is invalid.
-     * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN. Note each data
-     * connection can support multiple anyTypes.
-     *
-     * @hide
-     */
-    public void notifyDataConnectionFailed(int subId, int slotIndex, String apnType) {
-        try {
-            sRegistry.notifyDataConnectionFailedForSubscriber(slotIndex, subId, apnType);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
-    }
-
-    /**
      * TODO change from bundle to CellLocation?
      * @hide
      */
diff --git a/core/java/android/util/LongArrayQueue.java b/core/java/android/util/LongArrayQueue.java
index d5f0484..5c701db 100644
--- a/core/java/android/util/LongArrayQueue.java
+++ b/core/java/android/util/LongArrayQueue.java
@@ -162,4 +162,24 @@
         final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
         return mValues[index];
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        if (mSize <= 0) {
+            return "{}";
+        }
+
+        final StringBuilder buffer = new StringBuilder(mSize * 64);
+        buffer.append('{');
+        buffer.append(get(0));
+        for (int i = 1; i < mSize; i++) {
+            buffer.append(", ");
+            buffer.append(get(i));
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
 }
diff --git a/core/java/android/util/SparseSetArray.java b/core/java/android/util/SparseSetArray.java
index c1873d7..9f0f246 100644
--- a/core/java/android/util/SparseSetArray.java
+++ b/core/java/android/util/SparseSetArray.java
@@ -27,8 +27,8 @@
     }
 
     /**
-     * Add a value at index n.
-     * @return FALSE when the value already existed at the given index, TRUE otherwise.
+     * Add a value for key n.
+     * @return FALSE when the value already existed for the given key, TRUE otherwise.
      */
     public boolean add(int n, T value) {
         ArraySet<T> set = mData.get(n);
@@ -51,7 +51,7 @@
     }
 
     /**
-     * @return whether a value exists at index n.
+     * @return whether the value exists for the key n.
      */
     public boolean contains(int n, T value) {
         final ArraySet<T> set = mData.get(n);
@@ -62,15 +62,15 @@
     }
 
     /**
-     * @return the set of items at index n
+     * @return the set of items of key n
      */
     public ArraySet<T> get(int n) {
         return mData.get(n);
     }
 
     /**
-     * Remove a value from index n.
-     * @return TRUE when the value existed at the given index and removed, FALSE otherwise.
+     * Remove a value for key n.
+     * @return TRUE when the value existed for the given key and removed, FALSE otherwise.
      */
     public boolean remove(int n, T value) {
         final ArraySet<T> set = mData.get(n);
@@ -85,7 +85,7 @@
     }
 
     /**
-     * Remove all values from index n.
+     * Remove all values for key n.
      */
     public void remove(int n) {
         mData.remove(n);
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 28eb79a..71ac578 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,13 +16,13 @@
 
 package android.view;
 
-import static android.view.DisplayEventReceiver.CONFIG_CHANGED_EVENT_SUPPRESS;
 import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
 import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
 
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.graphics.FrameInfo;
+import android.graphics.Insets;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Build;
 import android.os.Handler;
@@ -219,9 +219,10 @@
     /**
      * Callback type: Animation callback to handle inset updates. This is separate from
      * {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via
-     * {@link WindowInsetsAnimationController#changeInsets} for multiple ongoing animations but then
-     * update the whole view system with a single callback to {@link View#dispatchWindowInsetsAnimationProgress}
-     * that contains all the combined updated insets.
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} for multiple
+     * ongoing animations but then update the whole view system with a single callback to
+     * {@link View#dispatchWindowInsetsAnimationProgress} that contains all the combined updated
+     * insets.
      * <p>
      * Both input and animation may change insets, so we need to run this after these callbacks, but
      * before traversals.
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 411508f..615dab0 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -66,6 +66,7 @@
     private static final String BOTTOM_MARKER = "@bottom";
     private static final String DP_MARKER = "@dp";
     private static final String RIGHT_MARKER = "@right";
+    private static final String LEFT_MARKER = "@left";
 
     /**
      * Category for overlays that allow emulating a display cutout on devices that don't have
@@ -647,6 +648,9 @@
         if (spec.endsWith(RIGHT_MARKER)) {
             offsetX = displayWidth;
             spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
+        } else if (spec.endsWith(LEFT_MARKER)) {
+            offsetX = 0;
+            spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim();
         } else {
             offsetX = displayWidth / 2f;
         }
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 3d139cd..cdfd397 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -21,7 +21,6 @@
 import static android.view.InsetsState.ISIDE_LEFT;
 import static android.view.InsetsState.ISIDE_RIGHT;
 import static android.view.InsetsState.ISIDE_TOP;
-import static android.view.InsetsState.toPublicType;
 
 import android.annotation.Nullable;
 import android.graphics.Insets;
@@ -34,7 +33,8 @@
 import android.view.InsetsState.InternalInsetsSide;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -66,23 +66,28 @@
     private final @InsetsType int mTypes;
     private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier;
     private final InsetsController mController;
-    private final WindowInsetsAnimationListener.InsetsAnimation mAnimation;
+    private final WindowInsetsAnimationCallback.InsetsAnimation mAnimation;
     private final Rect mFrame;
+    private final boolean mFade;
     private Insets mCurrentInsets;
     private Insets mPendingInsets;
+    private float mPendingFraction;
     private boolean mFinished;
     private boolean mCancelled;
-    private int mFinishedShownTypes;
+    private boolean mShownOnFinish;
+    private float mCurrentAlpha;
+    private float mPendingAlpha;
 
     @VisibleForTesting
     public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
             InsetsState state, WindowInsetsAnimationControlListener listener,
             @InsetsType int types,
             Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier,
-            InsetsController controller) {
+            InsetsController controller, long durationMs, boolean fade) {
         mConsumers = consumers;
         mListener = listener;
         mTypes = types;
+        mFade = fade;
         mTransactionApplierSupplier = transactionApplierSupplier;
         mController = controller;
         mInitialInsetsState = new InsetsState(state, true /* copySources */);
@@ -97,9 +102,11 @@
         // TODO: Check for controllability first and wait for IME if needed.
         listener.onReady(this, types);
 
-        mAnimation = new WindowInsetsAnimationListener.InsetsAnimation(mTypes, mHiddenInsets,
-                mShownInsets);
-        mController.dispatchAnimationStarted(mAnimation);
+        mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
+                InsetsController.INTERPOLATOR, durationMs);
+        mAnimation.setAlpha(getCurrentAlpha());
+        mController.dispatchAnimationStarted(mAnimation,
+                new AnimationBounds(mHiddenInsets, mShownInsets));
     }
 
     @Override
@@ -118,12 +125,17 @@
     }
 
     @Override
+    public float getCurrentAlpha() {
+        return mCurrentAlpha;
+    }
+
+    @Override
     @InsetsType public int getTypes() {
         return mTypes;
     }
 
     @Override
-    public void changeInsets(Insets insets) {
+    public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
         if (mFinished) {
             throw new IllegalStateException(
                     "Can't change insets on an animation that is finished.");
@@ -132,7 +144,9 @@
             throw new IllegalStateException(
                     "Can't change insets on an animation that is cancelled.");
         }
+        mPendingFraction = sanitize(fraction);
         mPendingInsets = sanitize(insets);
+        mPendingAlpha = 1 - sanitize(alpha);
         mController.scheduleApplyChangeInsets();
     }
 
@@ -145,40 +159,52 @@
             return false;
         }
         final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
+        final Float alphaOffset = 1 - mPendingAlpha;
         ArrayList<SurfaceParams> params = new ArrayList<>();
-        updateLeashesForSide(ISIDE_LEFT, offset.left, mPendingInsets.left, params, state);
-        updateLeashesForSide(ISIDE_TOP, offset.top, mPendingInsets.top, params, state);
-        updateLeashesForSide(ISIDE_RIGHT, offset.right, mPendingInsets.right, params, state);
-        updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mPendingInsets.bottom, params, state);
-        updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, params, state);
+        updateLeashesForSide(ISIDE_LEFT, offset.left, mShownInsets.left, mPendingInsets.left,
+                params, state, alphaOffset);
+        updateLeashesForSide(ISIDE_TOP, offset.top, mShownInsets.top, mPendingInsets.top, params,
+                state, alphaOffset);
+        updateLeashesForSide(ISIDE_RIGHT, offset.right, mShownInsets.right, mPendingInsets.right,
+                params, state, alphaOffset);
+        updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mShownInsets.bottom,
+                mPendingInsets.bottom, params, state, alphaOffset);
+        updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, 0 /* maxInset */,
+                params, state, alphaOffset);
 
         SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
         applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
         mCurrentInsets = mPendingInsets;
+        mAnimation.setFraction(mPendingFraction);
+        mCurrentAlpha = 1 - alphaOffset;
         if (mFinished) {
-            mController.notifyFinished(this, mFinishedShownTypes);
+            mController.notifyFinished(this, mShownOnFinish);
         }
         return mFinished;
     }
 
     @Override
-    public void finish(int shownTypes) {
+    public void finish(boolean shown) {
         if (mCancelled) {
             return;
         }
         InsetsState state = new InsetsState(mController.getState());
         for (int i = mConsumers.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = mConsumers.valueAt(i);
-            boolean visible = (shownTypes & toPublicType(consumer.getType())) != 0;
-            state.getSource(consumer.getType()).setVisible(visible);
+            state.getSource(consumer.getType()).setVisible(shown);
         }
         Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */);
-        changeInsets(insets);
+        setInsetsAndAlpha(insets, 1f /* alpha */, shown ? 1f : 0f /* fraction */);
         mFinished = true;
-        mFinishedShownTypes = shownTypes;
+        mShownOnFinish = shown;
     }
 
+    @Override
     @VisibleForTesting
+    public float getCurrentFraction() {
+        return mAnimation.getFraction();
+    }
+
     public void onCancelled() {
         if (mFinished) {
             return;
@@ -191,6 +217,10 @@
         return mAnimation;
     }
 
+    WindowInsetsAnimationControlListener getListener() {
+        return mListener;
+    }
+
     private Insets calculateInsets(InsetsState state, Rect frame,
             SparseArray<InsetsSourceConsumer> consumers, boolean shown,
             @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
@@ -210,11 +240,18 @@
     }
 
     private Insets sanitize(Insets insets) {
+        if (insets == null) {
+            insets = getCurrentInsets();
+        }
         return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets);
     }
 
+    private static float sanitize(float alpha) {
+        return alpha >= 1 ? 1 : (alpha <= 0 ? 0 : alpha);
+    }
+
     private void updateLeashesForSide(@InternalInsetsSide int side, int offset, int inset,
-            ArrayList<SurfaceParams> surfaceParams, InsetsState state) {
+            int maxInset, ArrayList<SurfaceParams> surfaceParams, InsetsState state, Float alpha) {
         ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side);
         if (items == null) {
             return;
@@ -238,7 +275,9 @@
 
             // If the system is controlling the insets source, the leash can be null.
             if (leash != null) {
-                surfaceParams.add(new SurfaceParams(leash, 1f /* alpha */, mTmpMatrix,
+                // TODO: use a better interpolation for fade.
+                alpha = mFade ? ((float) maxInset / inset * 0.3f + 0.7f) : alpha;
+                surfaceParams.add(new SurfaceParams(leash, alpha, mTmpMatrix,
                         null /* windowCrop */, 0 /* layer */, 0f /* cornerRadius*/,
                         side == ISIDE_FLOATING ? consumer.isVisible() : inset != 0 /* visible */));
             }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 43fec82..5563d62 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -39,6 +39,8 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
@@ -55,11 +57,12 @@
 
     private static final int ANIMATION_DURATION_SHOW_MS = 275;
     private static final int ANIMATION_DURATION_HIDE_MS = 340;
-    private static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
     private static final int DIRECTION_NONE = 0;
     private static final int DIRECTION_SHOW = 1;
     private static final int DIRECTION_HIDE = 2;
 
+    static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
     @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
     private @interface AnimationDirection{}
 
@@ -85,8 +88,75 @@
             return object.getCurrentInsets();
         }
         @Override
-        public void set(WindowInsetsAnimationController object, Insets value) {
-            object.changeInsets(value);
+        public void set(WindowInsetsAnimationController controller, Insets value) {
+            controller.setInsetsAndAlpha(
+                    value, 1f /* alpha */, (((DefaultAnimationControlListener)
+                            ((InsetsAnimationControlImpl) controller).getListener())
+                                    .getRawProgress()));
+        }
+    }
+
+    private class DefaultAnimationControlListener implements WindowInsetsAnimationControlListener {
+
+        private WindowInsetsAnimationController mController;
+        private ObjectAnimator mAnimator;
+        private boolean mShow;
+
+        DefaultAnimationControlListener(boolean show) {
+            mShow = show;
+        }
+
+        @Override
+        public void onReady(WindowInsetsAnimationController controller, int types) {
+            mController = controller;
+            if (mShow) {
+                showDirectly(types);
+            } else {
+                hideDirectly(types);
+            }
+            mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
+            mAnimator = ObjectAnimator.ofObject(
+                    controller,
+                    new InsetsProperty(),
+                    sEvaluator,
+                    mShow ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
+                    mShow ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
+            );
+            mAnimator.setDuration(getDurationMs());
+            mAnimator.setInterpolator(INTERPOLATOR);
+            mAnimator.addListener(new AnimatorListenerAdapter() {
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    onAnimationFinish();
+                }
+            });
+            mAnimator.start();
+        }
+
+        @Override
+        public void onCancelled() {
+            // Animator can be null when it is cancelled before onReady() completes.
+            if (mAnimator != null) {
+                mAnimator.cancel();
+            }
+        }
+
+        private void onAnimationFinish() {
+            mAnimationDirection = DIRECTION_NONE;
+            mController.finish(mShow);
+        }
+
+        private float getRawProgress() {
+            float fraction = (float) mAnimator.getCurrentPlayTime() / mAnimator.getDuration();
+            return mShow ? fraction : 1 - fraction;
+        }
+
+        private long getDurationMs() {
+            if (mAnimator != null) {
+                return mAnimator.getDuration();
+            }
+            return mShow ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS;
         }
     }
 
@@ -278,24 +348,25 @@
     }
 
     @Override
-    public void controlWindowInsetsAnimation(@InsetsType int types,
+    public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
             WindowInsetsAnimationControlListener listener) {
-        controlWindowInsetsAnimation(types, listener, false /* fromIme */);
+        controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs);
     }
 
     private void controlWindowInsetsAnimation(@InsetsType int types,
-            WindowInsetsAnimationControlListener listener, boolean fromIme) {
+            WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) {
         // If the frame of our window doesn't span the entire display, the control API makes very
         // little sense, as we don't deal with negative insets. So just cancel immediately.
         if (!mState.getDisplayFrame().equals(mFrame)) {
             listener.onCancelled();
             return;
         }
-        controlAnimationUnchecked(types, listener, mFrame, fromIme);
+        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
-            WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme) {
+            WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
+            long durationMs, boolean fade) {
         if (types == 0) {
             // nothing to animate.
             return;
@@ -326,7 +397,7 @@
 
         final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
                 frame, mState, listener, typesReady,
-                () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
+                () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this, durationMs, fade);
         mAnimationControls.add(controller);
     }
 
@@ -397,10 +468,13 @@
     }
 
     @VisibleForTesting
-    public void notifyFinished(InsetsAnimationControlImpl controller, int shownTypes) {
+    public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) {
         mAnimationControls.remove(controller);
-        hideDirectly(controller.getTypes() & ~shownTypes);
-        showDirectly(controller.getTypes() & shownTypes);
+        if (shown) {
+            showDirectly(controller.getTypes());
+        } else {
+            hideDirectly(controller.getTypes());
+        }
     }
 
     void notifyControlRevoked(InsetsSourceConsumer consumer) {
@@ -510,58 +584,12 @@
             return;
         }
 
-        WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
-
-            private WindowInsetsAnimationController mController;
-            private ObjectAnimator mAnimator;
-
-            @Override
-            public void onReady(WindowInsetsAnimationController controller, int types) {
-                mController = controller;
-                if (show) {
-                    showDirectly(types);
-                } else {
-                    hideDirectly(types);
-                }
-                mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
-                mAnimator = ObjectAnimator.ofObject(
-                        controller,
-                        new InsetsProperty(),
-                        sEvaluator,
-                        show ? controller.getHiddenStateInsets() : controller.getShownStateInsets(),
-                        show ? controller.getShownStateInsets() : controller.getHiddenStateInsets()
-                );
-                mAnimator.setDuration(show
-                        ? ANIMATION_DURATION_SHOW_MS
-                        : ANIMATION_DURATION_HIDE_MS);
-                mAnimator.setInterpolator(INTERPOLATOR);
-                mAnimator.addListener(new AnimatorListenerAdapter() {
-
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        onAnimationFinish();
-                    }
-                });
-                mAnimator.start();
-            }
-
-            @Override
-            public void onCancelled() {
-                // Animator can be null when it is cancelled before onReady() completes.
-                if (mAnimator != null) {
-                    mAnimator.cancel();
-                }
-            }
-
-            private void onAnimationFinish() {
-                mAnimationDirection = DIRECTION_NONE;
-                mController.finish(show ? types : 0);
-            }
-        };
-
+        final DefaultAnimationControlListener listener = new DefaultAnimationControlListener(show);
         // Show/hide animations always need to be relative to the display frame, in order that shown
         // and hidden state insets are correct.
-        controlAnimationUnchecked(types, listener, mState.getDisplayFrame(), fromIme);
+        controlAnimationUnchecked(
+                types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
+                true /* fade */);
     }
 
     private void hideDirectly(@InsetsType int types) {
@@ -592,12 +620,12 @@
     }
 
     @VisibleForTesting
-    public void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
-        mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
+    public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
+        mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
     }
 
     @VisibleForTesting
-    public void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+    public void dispatchAnimationFinished(InsetsAnimation animation) {
         mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 515dda6..1639dbe 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -109,7 +109,8 @@
 import android.view.AccessibilityIterators.TextSegmentIterator;
 import android.view.AccessibilityIterators.WordTextSegmentIterator;
 import android.view.ContextMenu.ContextMenuInfo;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityEventSource;
 import android.view.accessibility.AccessibilityManager;
@@ -4626,7 +4627,7 @@
 
         private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
 
-        private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
+        private WindowInsetsAnimationCallback mWindowInsetsAnimationCallback;
 
         /**
          * This lives here since it's only valid for interactive views.
@@ -11089,33 +11090,55 @@
     }
 
     /**
-     * Sets a {@link WindowInsetsAnimationListener} to be notified about animations of windows that
+     * Sets a {@link WindowInsetsAnimationCallback} to be notified about animations of windows that
      * cause insets.
      *
      * @param listener The listener to set.
-     * @hide pending unhide
      */
-    public void setWindowInsetsAnimationListener(WindowInsetsAnimationListener listener) {
-        getListenerInfo().mWindowInsetsAnimationListener = listener;
+    public void setWindowInsetsAnimationCallback(@Nullable WindowInsetsAnimationCallback listener) {
+        getListenerInfo().mWindowInsetsAnimationCallback = listener;
     }
 
-    void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
-        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
-            mListenerInfo.mWindowInsetsAnimationListener.onStarted(animation);
+    /**
+     * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)}
+     * when Window Insets animation is started.
+     * @param animation current animation
+     * @param bounds the upper and lower {@link AnimationBounds} that provides range of
+     *  {@link InsetsAnimation}.
+     * @return the upper and lower {@link AnimationBounds}.
+     */
+    @NonNull
+    public AnimationBounds dispatchWindowInsetsAnimationStarted(
+            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds);
         }
+        return bounds;
     }
 
-    WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
-        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
-            return mListenerInfo.mWindowInsetsAnimationListener.onProgress(insets);
+    /**
+     * Dispatches {@link WindowInsetsAnimationCallback#onProgress(WindowInsets)}
+     * when Window Insets animation makes progress.
+     * @param insets The current {@link WindowInsets}.
+     * @return current {@link WindowInsets}.
+     */
+    @NonNull
+    public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            return mListenerInfo.mWindowInsetsAnimationCallback.onProgress(insets);
         } else {
             return insets;
         }
     }
 
-    void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
-        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
-            mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation);
+    /**
+     * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)}
+     * when Window Insets animation finishes.
+     * @param animation The current ongoing {@link InsetsAnimation}.
+     */
+    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation);
         }
     }
 
@@ -11251,7 +11274,6 @@
      * @return The {@link WindowInsetsController} or {@code null} if the view isn't attached to a
      *         a window.
      * @see Window#getInsetsController()
-     * @hide pending unhide
      */
     public @Nullable WindowInsetsController getWindowInsetsController() {
         if (mAttachInfo != null) {
@@ -16161,7 +16183,7 @@
      * by the most recent call to {@link #measure(int, int)}.  This result is a bit mask
      * as defined by {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
      * This should be used during measurement and layout calculations only. Use
-     * {@link #getHeight()} to see how wide a view is after layout.
+     * {@link #getHeight()} to see how high a view is after layout.
      *
      * @return The measured height of this view as a bit mask.
      */
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 853a302..4334bb5 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -51,7 +51,8 @@
 import android.util.Pools.SynchronizedPool;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -7198,16 +7199,20 @@
     }
 
     @Override
-    void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
-        super.dispatchWindowInsetsAnimationStarted(animation);
+    @NonNull
+    public AnimationBounds dispatchWindowInsetsAnimationStarted(
+            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+        super.dispatchWindowInsetsAnimationStarted(animation, bounds);
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
-            getChildAt(i).dispatchWindowInsetsAnimationStarted(animation);
+            getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds);
         }
+        return bounds;
     }
 
     @Override
-    WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
+    @NonNull
+    public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets) {
         insets = super.dispatchWindowInsetsAnimationProgress(insets);
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
@@ -7217,7 +7222,7 @@
     }
 
     @Override
-    void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
+    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
         super.dispatchWindowInsetsAnimationFinished(animation);
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index af1882b..ff31115 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -2573,7 +2573,8 @@
     /**
      * @return The {@link WindowInsetsController} associated with this window
      * @see View#getWindowInsetsController()
-     * @hide pending unhide
      */
-    public abstract @NonNull WindowInsetsController getInsetsController();
+    public @Nullable WindowInsetsController getInsetsController() {
+        return null;
+    }
 }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index b16a4ca..2404c84 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -177,7 +177,7 @@
      * @return The insets that include system bars indicated by {@code typeMask}, taken from
      *         {@code typeInsetsMap}.
      */
-    private static Insets getInsets(Insets[] typeInsetsMap, @InsetsType int typeMask) {
+    static Insets getInsets(Insets[] typeInsetsMap, @InsetsType int typeMask) {
         Insets result = null;
         for (int i = FIRST; i <= LAST; i = i << 1) {
             if ((typeMask & i) == 0) {
@@ -289,9 +289,8 @@
      *
      * @param typeMask Bit mask of {@link InsetsType}s to query the insets for.
      * @return The insets.
-     *
-     * @hide pending unhide
      */
+    @NonNull
     public Insets getInsets(@InsetsType int typeMask) {
         return getInsets(mTypeInsetsMap, typeMask);
     }
@@ -313,8 +312,8 @@
      *                                  insets are not available for this type as the height of the
      *                                  IME is dynamic depending on the {@link EditorInfo} of the
      *                                  currently focused view, as well as the UI state of the IME.
-     * @hide pending unhide
      */
+    @NonNull
     public Insets getMaxInsets(@InsetsType int typeMask) throws IllegalArgumentException {
         if ((typeMask & IME) != 0) {
             throw new IllegalArgumentException("Unable to query the maximum insets for IME");
@@ -329,7 +328,6 @@
      * @param typeMask Bit mask of {@link Type.InsetsType}s to query visibility status.
      * @return {@code true} if and only if all windows included in {@code typeMask} are currently
      *         visible on screen.
-     * @hide pending unhide
      */
     public boolean isVisible(@InsetsType int typeMask) {
         for (int i = FIRST; i <= LAST; i = i << 1) {
@@ -874,7 +872,7 @@
         return typeInsetsMap;
     }
 
-    private static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
+    static Insets insetInsets(Insets insets, int left, int top, int right, int bottom) {
         int newLeft = Math.max(0, insets.left - left);
         int newTop = Math.max(0, insets.top - top);
         int newRight = Math.max(0, insets.right - right);
@@ -1015,7 +1013,6 @@
          * @param insets The insets to set.
          *
          * @return itself
-         * @hide pending unhide
          */
         @NonNull
         public Builder setInsets(@InsetsType int typeMask, @NonNull Insets insets) {
@@ -1046,7 +1043,6 @@
          *                                  the IME is dynamic depending on the {@link EditorInfo}
          *                                  of the currently focused view, as well as the UI
          *                                  state of the IME.
-         * @hide pending unhide
          */
         @NonNull
         public Builder setMaxInsets(@InsetsType int typeMask, @NonNull Insets insets)
@@ -1070,7 +1066,6 @@
          * @param visible Whether to mark the windows as visible or not.
          *
          * @return itself
-         * @hide pending unhide
          */
         @NonNull
         public Builder setVisible(@InsetsType int typeMask, boolean visible) {
@@ -1145,7 +1140,6 @@
 
     /**
      * Class that defines different types of sources causing window insets.
-     * @hide pending unhide
      */
     public static final class Type {
 
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
new file mode 100644
index 0000000..5e71f27
--- /dev/null
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.view.WindowInsets.Type.InsetsType;
+import android.view.animation.Interpolator;
+
+/**
+ * Interface that allows the application to listen to animation events for windows that cause
+ * insets.
+ */
+public interface WindowInsetsAnimationCallback {
+
+    /**
+     * Called when an inset animation gets started.
+     * <p>
+     * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical:
+     * It will starts at the root of the view hierarchy and then traverse it and invoke the callback
+     * of the specific {@link View} that is being traversed. The method my return a modified
+     * instance of the bounds by calling {@link AnimationBounds#inset} to indicate that a part of
+     * the insets have been used to offset or clip its children, and the children shouldn't worry
+     * about that part anymore.
+     *
+     * @param animation The animation that is about to start.
+     * @param bounds The bounds in which animation happens.
+     * @return The animation representing the part of the insets that should be dispatched to the
+     *         subtree of the hierarchy.
+     */
+    @NonNull
+    default AnimationBounds onStarted(
+            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+        return bounds;
+    }
+
+    /**
+     * Called when the insets change as part of running an animation. Note that even if multiple
+     * animations for different types are running, there will only be one progress callback per
+     * frame. The {@code insets} passed as an argument represents the overall state and will include
+     * all types, regardless of whether they are animating or not.
+     * <p>
+     * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
+     * and then traverse it and invoke the callback of the specific {@link View} being traversed.
+     * The method may return a modified instance by calling
+     * {@link WindowInsets#inset(int, int, int, int)} to indicate that a part of the insets have
+     * been used to offset or clip its children, and the children shouldn't worry about that part
+     * anymore.
+     * TODO: Introduce a way to map (type -> InsetAnimation) so app developer can query animation
+     *  for a given type e.g. callback.getAnimation(type) OR controller.getAnimation(type).
+     *  Or on the controller directly?
+     * @param insets The current insets.
+     * @return The insets to dispatch to the subtree of the hierarchy.
+     */
+    @NonNull
+    WindowInsets onProgress(@NonNull WindowInsets insets);
+
+    /**
+     * Called when an inset animation has finished.
+     *
+     * @param animation The animation that has finished running. This will be the same instance as
+     *                  passed into {@link #onStarted}
+     */
+    default void onFinished(@NonNull InsetsAnimation animation) {
+    }
+
+    /**
+     * Class representing an animation of a set of windows that cause insets.
+     */
+    final class InsetsAnimation {
+
+        private final @InsetsType int mTypeMask;
+        private float mFraction;
+        @Nullable private final Interpolator mInterpolator;
+        private long mDurationMs;
+        private float mAlpha;
+
+        public InsetsAnimation(
+                @InsetsType int typeMask, @Nullable Interpolator interpolator, long durationMs) {
+            mTypeMask = typeMask;
+            mInterpolator = interpolator;
+            mDurationMs = durationMs;
+        }
+
+        /**
+         * @return The bitmask of {@link WindowInsets.Type.InsetsType}s that are animating.
+         */
+        public @InsetsType int getTypeMask() {
+            return mTypeMask;
+        }
+
+        /**
+         * Returns the raw fractional progress of this animation between
+         * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+         * that this progress is the global progress of the animation, whereas
+         * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
+         * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+         * Progress per insets animation is global for the entire animation. One animation animates
+         * all things together (in, out, ...). If they don't animate together, we'd have
+         * multiple animations.
+         *
+         * @return The current progress of this animation.
+         */
+        @FloatRange(from = 0f, to = 1f)
+        public float getFraction() {
+            return mFraction;
+        }
+
+        /**
+         * Returns the interpolated fractional progress of this animation between
+         * {@link AnimationBounds#getLowerBound()} and {@link AnimationBounds#getUpperBound()}. Note
+         * that this progress is the global progress of the animation, whereas
+         * {@link WindowInsetsAnimationCallback#onProgress} will only dispatch the insets that may
+         * be inset with {@link WindowInsets#inset} by parents of views in the hierarchy.
+         * Progress per insets animation is global for the entire animation. One animation animates
+         * all things together (in, out, ...). If they don't animate together, we'd have
+         * multiple animations.
+         * @see #getFraction() for raw fraction.
+         * @return The current interpolated progress of this animation. -1 if interpolator isn't
+         * specified.
+         */
+        public float getInterpolatedFraction() {
+            if (mInterpolator != null) {
+                return mInterpolator.getInterpolation(mFraction);
+            }
+            return -1;
+        }
+
+        @Nullable
+        public Interpolator getInterpolator() {
+            return mInterpolator;
+        }
+
+        /**
+         * @return duration of animation in {@link java.util.concurrent.TimeUnit#MILLISECONDS}.
+         */
+        public long getDurationMillis() {
+            return mDurationMs;
+        }
+
+        /**
+         * Set fraction of the progress if {@link WindowInsets.Type.InsetsType} animation is
+         * controlled by the app {@see #getCurrentFraction}.
+         * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set progress either.
+         * Progress would be set by system with the system-default animation.
+         * </p>
+         * @param fraction fractional progress between 0 and 1 where 0 represents hidden and
+         *                zero progress and 1 represent fully shown final state.
+         */
+        public void setFraction(@FloatRange(from = 0f, to = 1f) float fraction) {
+            mFraction = fraction;
+        }
+
+        /**
+         * Set duration of the animation if {@link WindowInsets.Type.InsetsType} animation is
+         * controlled by the app.
+         * <p>Note: If app didn't create {@link InsetsAnimation}, it shouldn't set duration either.
+         * Duration would be set by system with the system-default animation.
+         * </p>
+         * @param durationMs in {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+         */
+        public void setDuration(long durationMs) {
+            mDurationMs = durationMs;
+        }
+
+        /**
+         * @return alpha of {@link WindowInsets.Type.InsetsType}.
+         */
+        @FloatRange(from = 0f, to = 1f)
+        public float getAlpha() {
+            return mAlpha;
+        }
+
+        void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) {
+            mAlpha = alpha;
+        }
+    }
+
+    /**
+     * Class representing the range of an {@link InsetsAnimation}
+     */
+    final class AnimationBounds {
+        private final Insets mLowerBound;
+        private final Insets mUpperBound;
+
+        public AnimationBounds(@NonNull Insets lowerBound, @NonNull Insets upperBound) {
+            mLowerBound = lowerBound;
+            mUpperBound = upperBound;
+        }
+
+        /**
+         * Queries the lower inset bound of the animation. If the animation is about showing or
+         * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
+         * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
+         * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+         * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+         * invoked because of an animation that originates from
+         * {@link WindowInsetsAnimationController}.
+         * <p>
+         * However, if the size of a window that causes insets is changing, these are the
+         * lower/upper bounds of that size animation.
+         * </p>
+         * There are no overlapping animations for a specific type, but there may be multiple
+         * animations running at the same time for different inset types.
+         *
+         * @see #getUpperBound()
+         * @see WindowInsetsAnimationController#getHiddenStateInsets
+         */
+        @NonNull
+        public Insets getLowerBound() {
+            return mLowerBound;
+        }
+
+        /**
+         * Queries the upper inset bound of the animation. If the animation is about showing or
+         * hiding a window that cause insets, the lower bound is {@link Insets#NONE}
+         * nd the upper bound is the same as {@link WindowInsets#getInsets(int)} for the fully
+         * shown state. This is the same as
+         * {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+         * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+         * invoked because of an animation that originates from
+         * {@link WindowInsetsAnimationController}.
+         * <p>
+         * However, if the size of a window that causes insets is changing, these are the
+         * lower/upper bounds of that size animation.
+         * <p>
+         * There are no overlapping animations for a specific type, but there may be multiple
+         * animations running at the same time for different inset types.
+         *
+         * @see #getLowerBound()
+         * @see WindowInsetsAnimationController#getShownStateInsets
+         */
+        @NonNull
+        public Insets getUpperBound() {
+            return mUpperBound;
+        }
+
+        /**
+         * 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
+         * 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
+         */
+        @NonNull
+        public AnimationBounds inset(@NonNull Insets insets) {
+            return new AnimationBounds(
+                    // TODO: refactor so that WindowInsets.insetInsets() is in a more appropriate
+                    //  place eventually.
+                    WindowInsets.insetInsets(
+                            mLowerBound, insets.left, insets.top, insets.right, insets.bottom),
+                    WindowInsets.insetInsets(
+                            mUpperBound, insets.left, insets.top, insets.right, insets.bottom));
+        }
+    }
+}
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 33fb327..8a226c1 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -22,19 +22,17 @@
 
 /**
  * Interface that informs the client about {@link WindowInsetsAnimationController} state changes.
- * @hide pending unhide
  */
 public interface WindowInsetsAnimationControlListener {
 
     /**
-     * Gets called as soon as 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.
+     * 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.
      *
      * @param controller The controller to control the inset animation.
      * @param types The {@link InsetsType}s it was able to gain control over. Note that this may be
      *              different than the types passed into
-     *              {@link WindowInsetsController#controlWindowInsetsAnimation} in case the window
+     *              {@link WindowInsetsController#controlInputMethodAnimation} in case the window
      *              wasn't able to gain the controls because it wasn't the IME target or not
      *              currently the window that's controlling the system bars.
      */
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 5cbf3b8..2bf0d27 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -16,83 +16,133 @@
 
 package android.view;
 
+import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.graphics.Insets;
 import android.view.WindowInsets.Type.InsetsType;
-import android.view.WindowInsetsAnimationListener.InsetsAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
 
 /**
- * Interface to control a window inset animation frame-by-frame.
- * @hide pending unhide
+ * Controller for app-driven animation of system windows.
+ *  <p>
+ *  {@code WindowInsetsAnimationController} lets apps animate system windows such as
+ *  the {@link android.inputmethodservice.InputMethodService IME}. The animation is
+ *  synchronized, such that changes the system windows and the app's current frame
+ *  are rendered at the same time.
+ *  <p>
+ *  Control is obtained through {@link WindowInsetsController#controlInputMethodAnimation}.
  */
+@SuppressLint("NotClosable")
 public interface WindowInsetsAnimationController {
 
     /**
      * Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden.
      * <p>
+     * Note that these insets are always relative to the window, which is the same as being relative
+     * to {@link View#getRootView}
+     * <p>
      * If there are any animation listeners registered, this value is the same as
-     * {@link InsetsAnimation#getLowerBound()} that will be passed into the callbacks.
+     * {@link AnimationBounds#getLowerBound()} that is being be passed into the root view of the
+     * hierarchy.
      *
      * @return Insets when the windows this animation is controlling are fully hidden.
      *
-     * @see InsetsAnimation#getLowerBound()
+     * @see AnimationBounds#getLowerBound()
      */
     @NonNull Insets getHiddenStateInsets();
 
     /**
      * Retrieves the {@link Insets} when the windows this animation is controlling are fully shown.
      * <p>
-     * In case the size of a window causing insets is changing in the middle of the animation, we
-     * execute that height change after this animation has finished.
+     * Note that these insets are always relative to the window, which is the same as being relative
+     * to {@link View#getRootView}
      * <p>
      * If there are any animation listeners registered, this value is the same as
-     * {@link InsetsAnimation#getUpperBound()} that will be passed into the callbacks.
+     * {@link AnimationBounds#getUpperBound()} that is being passed into the root view of hierarchy.
      *
      * @return Insets when the windows this animation is controlling are fully shown.
      *
-     * @see InsetsAnimation#getUpperBound()
+     * @see AnimationBounds#getUpperBound()
      */
     @NonNull Insets getShownStateInsets();
 
     /**
-     * @return The current insets on the window. These will follow any animation changes.
+     * Retrieves the current insets.
+     * <p>
+     * Note that these insets are always relative to the window, which is the same as
+     * being relative
+     * to {@link View#getRootView}
+     * @return The current insets on the currently showing frame. These insets will change as the
+     * animation progresses to reflect the current insets provided by the controlled window.
      */
     @NonNull Insets getCurrentInsets();
 
     /**
+     *  Returns the progress as previously set by {@code fraction} in {@link #setInsetsAndAlpha}
+     *
+     *  @return the progress of the animation, where {@code 0} is fully hidden and {@code 1} is
+     *  fully shown.
+     * <p>
+     *  Note: this value represents raw overall progress of the animation
+     *  i.e. the combined progress of insets and alpha.
+     *  <p>
+     */
+    @FloatRange(from = 0f, to = 1f)
+    float getCurrentFraction();
+
+    /**
+     * Current alpha value of the window.
+     * @return float value between 0 and 1.
+     */
+    float getCurrentAlpha();
+
+    /**
      * @return The {@link InsetsType}s this object is currently controlling.
      */
     @InsetsType int getTypes();
 
     /**
-     * Modifies the insets by indirectly moving the windows around in the system that are causing
-     * window insets.
+     * Modifies the insets for the frame being drawn by indirectly moving the windows around in the
+     * system that are causing window insets.
      * <p>
-     * Note that this will <b>not</b> inform the view system of a full inset change via
+     * Note that these insets are always relative to the window, which is the same as being relative
+     * to {@link View#getRootView}
+     * <p>
+     * Also note that this will <b>not</b> inform the view system of a full inset change via
      * {@link View#dispatchApplyWindowInsets} in order to avoid a full layout pass during the
      * animation. If you'd like to animate views during a window inset animation, register a
-     * {@link WindowInsetsAnimationListener} by calling
-     * {@link View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)} that will be
-     * notified about any insets change via {@link WindowInsetsAnimationListener#onProgress} during
+     * {@link WindowInsetsAnimationCallback} by calling
+     * {@link View#setWindowInsetsAnimationCallback(WindowInsetsAnimationCallback)} that will be
+     * notified about any insets change via {@link WindowInsetsAnimationCallback#onProgress} during
      * the animation.
      * <p>
      * {@link View#dispatchApplyWindowInsets} will instead be called once the animation has
      * finished, i.e. once {@link #finish} has been called.
+     * Note: If there are no insets, alpha animation is still applied.
      *
      * @param insets The new insets to apply. Based on the requested insets, the system will
      *               calculate the positions of the windows in the system causing insets such that
      *               the resulting insets of that configuration will match the passed in parameter.
      *               Note that these insets are being clamped to the range from
-     *               {@link #getHiddenStateInsets} to {@link #getShownStateInsets}
+     *               {@link #getHiddenStateInsets} to {@link #getShownStateInsets}.
+     *               If you intend on changing alpha only, pass null or {@link #getCurrentInsets()}.
+     * @param alpha  The new alpha to apply to the inset side.
+     * @param fraction instantaneous animation progress. This value is dispatched to
+     *                 {@link WindowInsetsAnimationCallback}.
      *
-     * @see WindowInsetsAnimationListener
-     * @see View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)
+     * @see WindowInsetsAnimationCallback
+     * @see View#setWindowInsetsAnimationCallback(WindowInsetsAnimationCallback)
      */
-    void changeInsets(@NonNull Insets insets);
+    void setInsetsAndAlpha(@Nullable Insets insets, @FloatRange(from = 0f, to = 1f) float alpha,
+            @FloatRange(from = 0f, to = 1f) float fraction);
 
     /**
-     * @param shownTypes The list of windows causing insets that should remain shown after finishing
-     *                   the animation.
+     * Finishes the animation, and leaves the windows shown or hidden. After invoking
+     * {@link #finish(boolean)}, this instance is no longer valid.
+     * @param shown if {@code true}, the windows will be shown after finishing the
+     *              animation. Otherwise they will be hidden.
      */
-    void finish(@InsetsType int shownTypes);
+    void finish(boolean shown);
 }
diff --git a/core/java/android/view/WindowInsetsAnimationListener.java b/core/java/android/view/WindowInsetsAnimationListener.java
deleted file mode 100644
index f734b4b..0000000
--- a/core/java/android/view/WindowInsetsAnimationListener.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.graphics.Insets;
-
-/**
- * Interface that allows the application to listen to animation events for windows that cause
- * insets.
- * @hide pending unhide
- */
-public interface WindowInsetsAnimationListener {
-
-    /**
-     * Called when an inset animation gets started.
-     *
-     * @param animation The animation that is about to start.
-     */
-    void onStarted(InsetsAnimation animation);
-
-    /**
-     * Called when the insets change as part of running an animation. Note that even if multiple
-     * animations for different types are running, there will only be one progress callback per
-     * frame. The {@code insets} passed as an argument represents the overall state and will include
-     * all types, regardless of whether they are animating or not.
-     * <p>
-     * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
-     * and then traverse it and invoke the callback of the specific {@link View} being traversed.
-     * The callback may return a modified instance by calling {@link WindowInsets#inset(int, int, int, int)}
-     * to indicate that a part of the insets have been used to offset or clip its children, and the
-     * children shouldn't worry about that part anymore.
-     *
-     * @param insets The current insets.
-     * @return The insets to dispatch to the subtree of the hierarchy.
-     */
-    WindowInsets onProgress(WindowInsets insets);
-
-    /**
-     * Called when an inset animation has finished.
-     *
-     * @param animation The animation that has finished running.
-     */
-    void onFinished(InsetsAnimation animation);
-
-    /**
-     * Class representing an animation of a set of windows that cause insets.
-     */
-    class InsetsAnimation {
-
-        private final @WindowInsets.Type.InsetsType int mTypeMask;
-        private final Insets mLowerBound;
-        private final Insets mUpperBound;
-
-        /**
-         * @hide
-         */
-        InsetsAnimation(int typeMask, Insets lowerBound, Insets upperBound) {
-            mTypeMask = typeMask;
-            mLowerBound = lowerBound;
-            mUpperBound = upperBound;
-        }
-
-        /**
-         * @return The bitmask of {@link WindowInsets.Type.InsetsType}s that are animating.
-         */
-        public @WindowInsets.Type.InsetsType int getTypeMask() {
-            return mTypeMask;
-        }
-
-        /**
-         * Queries the lower inset bound of the animation. If the animation is about showing or
-         * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
-         * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
-         * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
-         * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
-         * invoked because of an animation that originates from
-         * {@link WindowInsetsAnimationController}.
-         * <p>
-         * However, if the size of a window that causes insets is changing, these are the
-         * lower/upper bounds of that size animation.
-         * <p>
-         * There are no overlapping animations for a specific type, but there may be two animations
-         * running at the same time for different inset types.
-         *
-         * @see #getUpperBound()
-         * @see WindowInsetsAnimationController#getHiddenStateInsets
-         * TODO: It's a bit weird that these are global per window but onProgress is hierarchical.
-         * TODO: If multiple types are animating, querying the bound per type isn't possible. Should
-         * we:
-         * 1. Offer bounds by type here?
-         * 2. Restrict one animation to one single type only?
-         * Returning WindowInsets here isn't feasible in case of overlapping animations: We can't
-         * fill in the insets for the types from the other animation into the WindowInsets object
-         * as it's changing as well.
-         */
-        public Insets getLowerBound() {
-            return mLowerBound;
-        }
-
-        /**
-         * @see #getLowerBound()
-         * @see WindowInsetsAnimationController#getShownStateInsets
-         */
-        public Insets getUpperBound() {
-            return mUpperBound;
-        }
-    }
-}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index a045a6a..6de56be 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -30,7 +30,6 @@
  * Interface to control windows that generate insets.
  *
  * TODO Needs more information and examples once the API is more baked.
- * @hide pending unhide
  */
 public interface WindowInsetsController {
 
@@ -64,7 +63,10 @@
      */
     int APPEARANCE_LIGHT_NAVIGATION_BARS = 1 << 4;
 
-    /** Determines the appearance of system bars. */
+    /**
+     * Determines the appearance of system bars.
+     * @hide
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {APPEARANCE_OPAQUE_STATUS_BARS, APPEARANCE_OPAQUE_NAVIGATION_BARS,
             APPEARANCE_LOW_PROFILE_BARS, APPEARANCE_LIGHT_STATUS_BARS,
@@ -75,33 +77,40 @@
     /**
      * The default option for {@link #setSystemBarsBehavior(int)}. System bars will be forcibly
      * shown on any user interaction on the corresponding display if navigation bars are hidden by
-     * {@link #hide(int)} or {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+     * {@link #hide(int)} or
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
+     * @hide
      */
     int BEHAVIOR_SHOW_BARS_BY_TOUCH = 0;
 
     /**
      * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
      * hiding navigation bars by calling {@link #hide(int)} or
-     * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
      *
      * <p>When system bars are hidden in this mode, they can be revealed with system gestures, such
      * as swiping from the edge of the screen where the bar is hidden from.</p>
+     * @hide
      */
     int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1;
 
     /**
      * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
      * hiding navigation bars by calling {@link #hide(int)} or
-     * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+     * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)}.
      *
      * <p>When system bars are hidden in this mode, they can be revealed temporarily with system
      * gestures, such as swiping from the edge of the screen where the bar is hidden from. These
      * transient system bars will overlay app’s content, may have some degree of transparency, and
      * will automatically hide after a short timeout.</p>
+     * @hide
      */
     int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2;
 
-    /** Determines the behavior of system bars when hiding them by calling {@link #hide}. */
+    /**
+     * Determines the behavior of system bars when hiding them by calling {@link #hide}.
+     * @hide
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {BEHAVIOR_SHOW_BARS_BY_TOUCH, BEHAVIOR_SHOW_BARS_BY_SWIPE,
             BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE})
@@ -139,23 +148,27 @@
      * the position of the windows in the system causing insets directly.
      *
      * @param types The {@link InsetsType}s the application has requested to control.
+     * @param durationMillis duration of animation in
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 windows are ready to be controlled, among other callbacks.
      * @hide
      */
-    void controlWindowInsetsAnimation(@InsetsType int types,
+    void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
             @NonNull WindowInsetsAnimationControlListener listener);
 
     /**
      * Lets the application control the animation for showing the IME in a frame-by-frame manner by
      * modifying the position of the IME when it's causing insets.
      *
+     * @param durationMillis duration of the animation in
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 IME are ready to be controlled, among other callbacks.
      */
-    default void controlInputMethodAnimation(
+    default void controlInputMethodAnimation(long durationMillis,
             @NonNull WindowInsetsAnimationControlListener listener) {
-        controlWindowInsetsAnimation(ime(), listener);
+        controlWindowInsetsAnimation(ime(), durationMillis, listener);
     }
 
     /**
@@ -166,7 +179,7 @@
      * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
      * {@link WindowInsets#isVisible}.
      *
-     * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+     * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
      * @see #hideInputMethod()
      */
     default void showInputMethod() {
@@ -181,7 +194,7 @@
      * the event by observing {@link View#onApplyWindowInsets} and checking visibility with
      * {@link WindowInsets#isVisible}.
      *
-     * @see #controlInputMethodAnimation(WindowInsetsAnimationControlListener)
+     * @see #controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)
      * @see #showInputMethod()
      */
     default void hideInputMethod() {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 34092e2..3b6c55b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1313,14 +1313,11 @@
          * set for you by Window as described in {@link Window#setFlags}.*/
         public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
 
-        /** Window flag: invert the state of {@link #FLAG_NOT_FOCUSABLE} with
-         * respect to how this window interacts with the current method.  That
-         * is, if FLAG_NOT_FOCUSABLE is set and this flag is set, then the
-         * window will behave as if it needs to interact with the input method
-         * and thus be placed behind/away from it; if FLAG_NOT_FOCUSABLE is
-         * not set and this flag is set, then the window will behave as if it
-         * doesn't need to interact with the input method and can be placed
-         * to use more space and cover the input method.
+        /** Window flag: When set, input method can't interact with the focusable window
+         * and can be placed to use more space and cover the input method.
+         * Note: When combined with {@link #FLAG_NOT_FOCUSABLE}, this flag has no
+         * effect since input method cannot interact with windows having {@link #FLAG_NOT_FOCUSABLE}
+         * flag set.
          */
         public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
 
@@ -1998,16 +1995,12 @@
          *
          * @param flags The current window manager flags.
          *
-         * @return Returns true if such a window should be behind/interact
-         * with an input method, false if not.
+         * @return Returns {@code true} if such a window should be behind/interact
+         * with an input method, {@code false} if not.
          */
         public static boolean mayUseInputMethod(int flags) {
-            switch (flags&(FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)) {
-                case 0:
-                case FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM:
-                    return true;
-            }
-            return false;
+            return (flags & FLAG_NOT_FOCUSABLE) != FLAG_NOT_FOCUSABLE
+                    && (flags & FLAG_ALT_FOCUSABLE_IM) != FLAG_ALT_FOCUSABLE_IM;
         }
 
         /**
diff --git a/core/java/android/view/inline/InlineContentView.java b/core/java/android/view/inline/InlineContentView.java
index 4bc2176..b143fad 100644
--- a/core/java/android/view/inline/InlineContentView.java
+++ b/core/java/android/view/inline/InlineContentView.java
@@ -50,7 +50,10 @@
 
             @Override
             public void surfaceDestroyed(SurfaceHolder holder) {
-                // TODO(b/137800469): implement this.
+                new SurfaceControl.Transaction()
+                        .setVisibility(surfaceControl, false)
+                        .reparent(surfaceControl, null)
+                        .apply();
             }
         });
     }
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index 25e34d3..c10144e 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -271,7 +271,7 @@
     };
 
     @DataClass.Generated(
-            time = 1574446220398L,
+            time = 1575933636929L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
             inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic  void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 112653a..1e5a3b0 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -21,11 +21,16 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.util.Log;
+import android.view.autofill.AutofillId;
 
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
 
 /**
  * The InputMethod interface represents an input method which can generate key
@@ -104,6 +109,20 @@
     }
 
     /**
+     * Called to notify the IME that Autofill Frameworks requested an inline suggestions request.
+     *
+     * @hide
+     */
+    default void onCreateInlineSuggestionsRequest(ComponentName componentName,
+            AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+        try {
+            cb.onInlineSuggestionsUnsupported();
+        } catch (RemoteException e) {
+            Log.w("InputMethod", "RemoteException calling onInlineSuggestionsUnsupported: " + e);
+        }
+    }
+
+    /**
      * Called first thing after an input method is created, this supplies a
      * unique token for the session it has with the system service.  It is
      * needed to identify itself with the service to validate its operations.
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index d44fbd7..3e26f63 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 
 /**
@@ -38,19 +39,19 @@
  * guide.</p>
  *
  * <p><strong>XML attributes</strong></p>
- * <p> 
- * See {@link android.R.styleable#CompoundButton CompoundButton Attributes}, 
- * {@link android.R.styleable#Button Button Attributes}, 
- * {@link android.R.styleable#TextView TextView Attributes}, 
+ * <p>
+ * See {@link android.R.styleable#CompoundButton CompoundButton Attributes},
+ * {@link android.R.styleable#Button Button Attributes},
+ * {@link android.R.styleable#TextView TextView Attributes},
  * {@link android.R.styleable#View View Attributes}
  * </p>
  */
 public class RadioButton extends CompoundButton {
-    
+
     public RadioButton(Context context) {
         this(context, null);
     }
-    
+
     public RadioButton(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.radioButtonStyle);
     }
@@ -81,4 +82,20 @@
     public CharSequence getAccessibilityClassName() {
         return RadioButton.class.getName();
     }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        if (getParent() instanceof RadioGroup) {
+            RadioGroup radioGroup = (RadioGroup) getParent();
+            if (radioGroup.getOrientation() == LinearLayout.HORIZONTAL) {
+                info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(0, 1,
+                        radioGroup.getIndexWithinVisibleButtons(this), 1, false, isChecked()));
+            } else {
+                info.setCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo.obtain(
+                        radioGroup.getIndexWithinVisibleButtons(this), 1, 0, 1,
+                        false, isChecked()));
+            }
+        }
+    }
 }
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index c62c16c..90bc0a3 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IdRes;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -26,6 +27,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStructure;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
 
@@ -93,6 +95,7 @@
         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
         }
+        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
 
         // retrieve selected radio button as requested by the user in the
         // XML layout file
@@ -475,4 +478,50 @@
         }
         return null;
     }
-}
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        if (this.getOrientation() == HORIZONTAL) {
+            info.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(1,
+                    getVisibleChildCount(), false,
+                    AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+        } else {
+            info.setCollectionInfo(
+                    AccessibilityNodeInfo.CollectionInfo.obtain(getVisibleChildCount(),
+                    1, false,
+                    AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE));
+        }
+    }
+
+    private int getVisibleChildCount() {
+        int count = 0;
+        for (int i = 0; i < getChildCount(); i++) {
+            if (this.getChildAt(i) instanceof RadioButton) {
+                if (((RadioButton) this.getChildAt(i)).getVisibility() == VISIBLE) {
+                    count++;
+                }
+            }
+        }
+        return count;
+    }
+
+    int getIndexWithinVisibleButtons(@Nullable View child) {
+        if (!(child instanceof RadioButton)) {
+            return -1;
+        }
+        int index = 0;
+        for (int i = 0; i < getChildCount(); i++) {
+            if (this.getChildAt(i) instanceof RadioButton) {
+                RadioButton radioButton = (RadioButton) this.getChildAt(i);
+                if (radioButton == child) {
+                    return index;
+                }
+                if (radioButton.getVisibility() == VISIBLE) {
+                    index++;
+                }
+            }
+        }
+        return -1;
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c571737..a6304b1 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -3473,18 +3473,10 @@
         return applyAsync(context, parent, executor, listener, null);
     }
 
-    private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) {
-        CancellationSignal cancelSignal = new CancellationSignal();
-        cancelSignal.setOnCancelListener(task);
-
-        task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
-        return cancelSignal;
-    }
-
     /** @hide */
     public CancellationSignal applyAsync(Context context, ViewGroup parent,
             Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
-        return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor);
+        return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor);
     }
 
     private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
@@ -3495,6 +3487,7 @@
 
     private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
             implements CancellationSignal.OnCancelListener {
+        final CancellationSignal mCancelSignal = new CancellationSignal();
         final RemoteViews mRV;
         final ViewGroup mParent;
         final Context mContext;
@@ -3545,6 +3538,7 @@
 
         @Override
         protected void onPostExecute(ViewTree viewTree) {
+            mCancelSignal.setOnCancelListener(null);
             if (mError == null) {
                 if (mListener != null) {
                     mListener.onViewInflated(viewTree.mRoot);
@@ -3582,6 +3576,12 @@
         public void onCancel() {
             cancel(true);
         }
+
+        private CancellationSignal startTaskOnExecutor(Executor executor) {
+            mCancelSignal.setOnCancelListener(this);
+            executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
+            return mCancelSignal;
+        }
     }
 
     /**
@@ -3646,8 +3646,8 @@
             }
         }
 
-        return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
-                context, listener, handler, v), executor);
+        return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
+                context, listener, handler, v).startTaskOnExecutor(executor);
     }
 
     private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
diff --git a/telephony/java/com/android/ims/internal/uce/common/CapInfo.aidl b/core/java/com/android/ims/internal/uce/common/CapInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/CapInfo.aidl
rename to core/java/com/android/ims/internal/uce/common/CapInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/CapInfo.java
rename to core/java/com/android/ims/internal/uce/common/CapInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/common/StatusCode.aidl b/core/java/com/android/ims/internal/uce/common/StatusCode.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/StatusCode.aidl
rename to core/java/com/android/ims/internal/uce/common/StatusCode.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/common/StatusCode.java b/core/java/com/android/ims/internal/uce/common/StatusCode.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/StatusCode.java
rename to core/java/com/android/ims/internal/uce/common/StatusCode.java
diff --git a/telephony/java/com/android/ims/internal/uce/common/UceLong.aidl b/core/java/com/android/ims/internal/uce/common/UceLong.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/UceLong.aidl
rename to core/java/com/android/ims/internal/uce/common/UceLong.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/common/UceLong.java b/core/java/com/android/ims/internal/uce/common/UceLong.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/common/UceLong.java
rename to core/java/com/android/ims/internal/uce/common/UceLong.java
diff --git a/telephony/java/com/android/ims/internal/uce/options/IOptionsListener.aidl b/core/java/com/android/ims/internal/uce/options/IOptionsListener.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/IOptionsListener.aidl
rename to core/java/com/android/ims/internal/uce/options/IOptionsListener.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/IOptionsService.aidl b/core/java/com/android/ims/internal/uce/options/IOptionsService.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/IOptionsService.aidl
rename to core/java/com/android/ims/internal/uce/options/IOptionsService.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCapInfo.aidl b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCapInfo.aidl
rename to core/java/com/android/ims/internal/uce/options/OptionsCapInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCapInfo.java b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
rename to core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCmdId.aidl b/core/java/com/android/ims/internal/uce/options/OptionsCmdId.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCmdId.aidl
rename to core/java/com/android/ims/internal/uce/options/OptionsCmdId.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCmdId.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdId.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCmdId.java
rename to core/java/com/android/ims/internal/uce/options/OptionsCmdId.java
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCmdStatus.aidl b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCmdStatus.aidl
rename to core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
rename to core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsSipResponse.aidl b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsSipResponse.aidl
rename to core/java/com/android/ims/internal/uce/options/OptionsSipResponse.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/options/OptionsSipResponse.java b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
rename to core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl b/core/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl
rename to core/java/com/android/ims/internal/uce/presence/IPresenceListener.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/IPresenceService.aidl b/core/java/com/android/ims/internal/uce/presence/IPresenceService.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/IPresenceService.aidl
rename to core/java/com/android/ims/internal/uce/presence/IPresenceService.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCapInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCapInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresCapInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCapInfo.java b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCapInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCmdId.aidl b/core/java/com/android/ims/internal/uce/presence/PresCmdId.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCmdId.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresCmdId.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCmdId.java b/core/java/com/android/ims/internal/uce/presence/PresCmdId.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCmdId.java
rename to core/java/com/android/ims/internal/uce/presence/PresCmdId.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCmdStatus.aidl b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCmdStatus.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresCmdStatus.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresCmdStatus.java b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
rename to core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.aidl b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
rename to core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresResInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresResInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresResInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresResInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresResInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresResInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresResInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresRlmiInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresRlmiInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresServiceInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresServiceInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresServiceInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresServiceInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresServiceInfo.java b/core/java/com/android/ims/internal/uce/presence/PresServiceInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresServiceInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresServiceInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresSipResponse.aidl b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresSipResponse.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresSipResponse.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresSipResponse.java b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresSipResponse.java
rename to core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresSubscriptionState.aidl b/core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresSubscriptionState.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java b/core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java
rename to core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresTupleInfo.aidl b/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresTupleInfo.aidl
rename to core/java/com/android/ims/internal/uce/presence/PresTupleInfo.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/presence/PresTupleInfo.java b/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/presence/PresTupleInfo.java
rename to core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl b/core/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl
rename to core/java/com/android/ims/internal/uce/uceservice/IUceListener.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl b/core/java/com/android/ims/internal/uce/uceservice/IUceService.aidl
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/uceservice/IUceService.aidl
rename to core/java/com/android/ims/internal/uce/uceservice/IUceService.aidl
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
rename to core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
diff --git a/telephony/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java b/core/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java
similarity index 100%
rename from telephony/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java
rename to core/java/com/android/ims/internal/uce/uceservice/UceServiceBase.java
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 8856f99..f361784 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -111,6 +111,7 @@
 import com.android.internal.app.ResolverListAdapter.ViewHolder;
 import com.android.internal.app.chooser.ChooserTargetInfo;
 import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.MultiDisplayResolveInfo;
 import com.android.internal.app.chooser.NotSelectableTargetInfo;
 import com.android.internal.app.chooser.SelectableTargetInfo;
 import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
@@ -1352,17 +1353,31 @@
         return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
     }
 
-    @Override
-    public void showTargetDetails(ResolveInfo ri) {
-        if (ri == null) {
+    void showTargetDetails(TargetInfo ti) {
+        if (ti == null) {
             return;
         }
-
-        ComponentName name = ri.activityInfo.getComponentName();
+        ComponentName name = ti.getResolveInfo().activityInfo.getComponentName();
         boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
-        ResolverTargetActionsDialogFragment f =
-                new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
-                        name, pinned);
+
+        ResolverTargetActionsDialogFragment f;
+
+        // For multiple targets, include info on all targets
+        if (ti instanceof MultiDisplayResolveInfo) {
+            MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) ti;
+            List<CharSequence> labels = new ArrayList<>();
+
+            for (TargetInfo innerInfo : mti.getTargets()) {
+                labels.add(innerInfo.getResolveInfo().loadLabel(getPackageManager()));
+            }
+            f = new ResolverTargetActionsDialogFragment(
+                    mti.getResolveInfo().loadLabel(getPackageManager()), name, mti.getTargets(),
+                    labels);
+        } else {
+            f = new ResolverTargetActionsDialogFragment(
+                    ti.getResolveInfo().loadLabel(getPackageManager()), name, pinned);
+        }
+
         f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
     }
 
@@ -1416,8 +1431,26 @@
         }
 
         final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
+
+        // Stacked apps get a disambiguation first
+        if (targetInfo instanceof MultiDisplayResolveInfo) {
+            MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
+            CharSequence[] labels = new CharSequence[mti.getTargets().size()];
+            int i = 0;
+            for (TargetInfo ti : mti.getTargets()) {
+                labels[i++] = ti.getResolveInfo().loadLabel(getPackageManager());
+            }
+            ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(
+                    targetInfo.getDisplayLabel(),
+                    ((MultiDisplayResolveInfo) targetInfo).getTargets(), labels);
+
+            f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
+            return;
+        }
+
         super.startSelected(which, always, filtered);
 
+
         if (currentListAdapter.getCount() > 0) {
             // Log the index of which type of target the user picked.
             // Lower values mean the ranking was better.
@@ -2363,7 +2396,7 @@
                 itemView.setOnLongClickListener(v -> {
                     showTargetDetails(
                             mChooserMultiProfilePagerAdapter.getActiveListAdapter()
-                                    .resolveInfoForPosition(mListPosition, /* filtered */ true));
+                                    .targetInfoForPosition(mListPosition, /* filtered */ true));
                     return true;
                 });
             }
@@ -2615,7 +2648,7 @@
                     @Override
                     public boolean onLongClick(View v) {
                         showTargetDetails(
-                                mChooserListAdapter.resolveInfoForPosition(
+                                mChooserListAdapter.targetInfoForPosition(
                                         holder.getItemIndex(column), true));
                         return true;
                     }
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 4eccf21..a8a676d 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -38,17 +38,22 @@
 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
 import com.android.internal.app.chooser.ChooserTargetInfo;
 import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.MultiDisplayResolveInfo;
 import com.android.internal.app.chooser.SelectableTargetInfo;
 import com.android.internal.app.chooser.TargetInfo;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class ChooserListAdapter extends ResolverListAdapter {
     private static final String TAG = "ChooserListAdapter";
     private static final boolean DEBUG = false;
 
+    private boolean mEnableStackedApps = true;
+
     public static final int NO_POSITION = -1;
     public static final int TARGET_BAD = -1;
     public static final int TARGET_CALLER = 0;
@@ -218,7 +223,25 @@
 
     void updateAlphabeticalList() {
         mSortedList.clear();
-        mSortedList.addAll(mDisplayList);
+        if (mEnableStackedApps) {
+            // Consolidate multiple targets from same app.
+            Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
+            for (DisplayResolveInfo info : mDisplayList) {
+                String packageName = info.getResolvedComponentName().getPackageName();
+                if (consolidated.get(packageName) != null) {
+                    // create consolidated target
+                    MultiDisplayResolveInfo multiDisplayResolveInfo =
+                            new MultiDisplayResolveInfo(packageName, info);
+                    multiDisplayResolveInfo.addTarget(consolidated.get(packageName));
+                    consolidated.put(packageName, multiDisplayResolveInfo);
+                } else {
+                    consolidated.put(packageName, info);
+                }
+            }
+            mSortedList.addAll(consolidated.values());
+        } else {
+            mSortedList.addAll(mDisplayList);
+        }
         Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext));
     }
 
@@ -270,7 +293,7 @@
     }
 
     int getAlphaTargetCount() {
-        int standardCount = super.getCount();
+        int standardCount = mSortedList.size();
         return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0;
     }
 
diff --git a/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java
new file mode 100644
index 0000000..ff6582d
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.internal.app;
+
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+import com.android.internal.app.chooser.DisplayResolveInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows individual actions for a "stacked" app target - such as an app with multiple posting
+ * streams represented in the Sharesheet.
+ */
+public class ChooserStackedAppDialogFragment extends DialogFragment
+        implements DialogInterface.OnClickListener {
+    private static final String TITLE_KEY = "title";
+    private static final String PINNED_KEY = "pinned";
+
+    private List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
+    private CharSequence[] mLabels;
+
+    public ChooserStackedAppDialogFragment() {
+    }
+
+    public ChooserStackedAppDialogFragment(CharSequence title) {
+        Bundle args = new Bundle();
+        args.putCharSequence(TITLE_KEY, title);
+        setArguments(args);
+    }
+
+    public ChooserStackedAppDialogFragment(CharSequence title,
+            List<DisplayResolveInfo> targets, CharSequence[] labels) {
+        Bundle args = new Bundle();
+        args.putCharSequence(TITLE_KEY, title);
+        mTargetInfos = targets;
+        mLabels = labels;
+        setArguments(args);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final Bundle args = getArguments();
+        return new Builder(getContext())
+                .setCancelable(true)
+                .setItems(mLabels, this)
+                .setTitle(args.getCharSequence(TITLE_KEY))
+                .create();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        final Bundle args = getArguments();
+        mTargetInfos.get(which).start(getActivity(), null);
+        dismiss();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        // Dismiss on config changed (eg: rotation)
+        // TODO: Maintain state on config change
+        super.onConfigurationChanged(newConfig);
+        dismiss();
+    }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index cb7f2e4..8dc3a07 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1160,7 +1160,7 @@
         return !target.isSuspended();
     }
 
-    public void showTargetDetails(ResolveInfo ri) {
+    void showTargetDetails(ResolveInfo ri) {
         Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
index df91c4a..bdbe210 100644
--- a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
+++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
@@ -24,14 +24,19 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.content.res.Configuration;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
 
 import com.android.internal.R;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
- * Shows a dialog with actions to take on a chooser target
+ * Shows a dialog with actions to take on a chooser target.
  */
 public class ResolverTargetActionsDialogFragment extends DialogFragment
         implements DialogInterface.OnClickListener {
@@ -43,6 +48,10 @@
     private static final int TOGGLE_PIN_INDEX = 0;
     private static final int APP_INFO_INDEX = 1;
 
+    private List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
+    private List<CharSequence> mLabels = new ArrayList<>();
+    private boolean[] mPinned;
+
     public ResolverTargetActionsDialogFragment() {
     }
 
@@ -55,15 +64,43 @@
         setArguments(args);
     }
 
+    public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
+            List<DisplayResolveInfo> targets, List<CharSequence> labels) {
+        Bundle args = new Bundle();
+        args.putCharSequence(TITLE_KEY, title);
+        args.putParcelable(NAME_KEY, name);
+        mTargetInfos = targets;
+        mLabels = labels;
+        setArguments(args);
+    }
+
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         final Bundle args = getArguments();
         final int itemRes = args.getBoolean(PINNED_KEY, false)
                 ? R.array.resolver_target_actions_unpin
                 : R.array.resolver_target_actions_pin;
+        String[] defaultActions = getResources().getStringArray(itemRes);
+        CharSequence[] items;
+
+        if (mTargetInfos == null || mTargetInfos.size() < 2) {
+            items = defaultActions;
+        } else {
+            // Pin item for each sub-item
+            items = new CharSequence[mTargetInfos.size() + 1];
+            for (int i = 0; i < mTargetInfos.size(); i++) {
+                items[i] = mTargetInfos.get(i).isPinned()
+                         ? getResources().getString(R.string.unpin_specific_target, mLabels.get(i))
+                         : getResources().getString(R.string.pin_specific_target, mLabels.get(i));
+            }
+            // "App info"
+            items[mTargetInfos.size()] = defaultActions[1];
+        }
+
+
         return new Builder(getContext())
                 .setCancelable(true)
-                .setItems(itemRes, this)
+                .setItems(items, this)
                 .setTitle(args.getCharSequence(TITLE_KEY))
                 .create();
     }
@@ -72,27 +109,41 @@
     public void onClick(DialogInterface dialog, int which) {
         final Bundle args = getArguments();
         ComponentName name = args.getParcelable(NAME_KEY);
-        switch (which) {
-            case TOGGLE_PIN_INDEX:
-                SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
-                final String key = name.flattenToString();
-                boolean currentVal = sp.getBoolean(name.flattenToString(), false);
-                if (currentVal) {
-                    sp.edit().remove(key).apply();
-                } else {
-                    sp.edit().putBoolean(key, true).apply();
-                }
-
-                // Force the chooser to requery and resort things
-                getActivity().recreate();
-                break;
-            case APP_INFO_INDEX:
-                Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
-                        .setData(Uri.fromParts("package", name.getPackageName(), null))
-                        .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
-                startActivity(in);
-                break;
+        if (which == 0 || (mTargetInfos.size() > 0 && which < mTargetInfos.size())) {
+            if (mTargetInfos == null || mTargetInfos.size() == 0) {
+                pinComponent(name);
+            } else {
+                pinComponent(mTargetInfos.get(which).getResolvedComponentName());
+            }
+            // Force the chooser to requery and resort things
+            getActivity().recreate();
+        } else {
+            // Last item in dialog is App Info
+            Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+                    .setData(Uri.fromParts("package", name.getPackageName(), null))
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+            startActivity(in);
         }
         dismiss();
     }
+
+    private void pinComponent(ComponentName name) {
+        SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
+        final String key = name.flattenToString();
+        boolean currentVal = sp.getBoolean(name.flattenToString(), false);
+        if (currentVal) {
+            sp.edit().remove(key).apply();
+        } else {
+            sp.edit().putBoolean(key, true).apply();
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        // Dismiss on config changed (eg: rotation)
+        // TODO: Maintain state on config change
+        super.onConfigurationChanged(newConfig);
+        dismiss();
+    }
+
 }
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index f92637c..86a9af3 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -91,6 +91,16 @@
         mResolveInfoPresentationGetter = resolveInfoPresentationGetter;
     }
 
+    DisplayResolveInfo(DisplayResolveInfo other) {
+        mSourceIntents.addAll(other.getAllSourceIntents());
+        mResolveInfo = other.mResolveInfo;
+        mDisplayLabel = other.mDisplayLabel;
+        mDisplayIcon = other.mDisplayIcon;
+        mExtendedInfo = other.mExtendedInfo;
+        mResolvedIntent = other.mResolvedIntent;
+        mResolveInfoPresentationGetter = other.mResolveInfoPresentationGetter;
+    }
+
     public ResolveInfo getResolveInfo() {
         return mResolveInfo;
     }
@@ -189,4 +199,5 @@
     public void setPinned(boolean pinned) {
         mPinned = pinned;
     }
+
 }
diff --git a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
new file mode 100644
index 0000000..4c52411
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app.chooser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a "stack" of chooser targets for various activities within the same component.
+ */
+public class MultiDisplayResolveInfo extends DisplayResolveInfo {
+
+    List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
+    String mPackageName;
+    // We'll use this DRI for basic presentation info - eg icon, name.
+    final DisplayResolveInfo mBaseInfo;
+
+    /**
+     * @param firstInfo A representative DRI to use for the main icon, title, etc for this Info.
+     */
+    public MultiDisplayResolveInfo(String packageName, DisplayResolveInfo firstInfo) {
+        super(firstInfo);
+        mBaseInfo = firstInfo;
+        mTargetInfos.add(firstInfo);
+    }
+
+    /**
+     * Add another DisplayResolveInfo to the list included for this target.
+     */
+    public void addTarget(DisplayResolveInfo target) {
+        mTargetInfos.add(target);
+    }
+
+    /**
+     * List of all DisplayResolveInfos included in this target.
+     */
+    public List<DisplayResolveInfo> getTargets() {
+        return mTargetInfos;
+    }
+
+}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index f5708a5c..dec9ae7 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -259,7 +259,7 @@
                 throw new IllegalStateException("Failed to touch " + file + ": " + e);
             }
         }
-        MediaStore.scanFile(getContext(), file);
+        MediaStore.scanFile(getContext().getContentResolver(), file);
 
         return childId;
     }
@@ -316,10 +316,10 @@
 
     private void moveInMediaStore(@Nullable File oldVisibleFile, @Nullable File newVisibleFile) {
         if (oldVisibleFile != null) {
-            MediaStore.scanFile(getContext(), oldVisibleFile);
+            MediaStore.scanFile(getContext().getContentResolver(), oldVisibleFile);
         }
         if (newVisibleFile != null) {
-            MediaStore.scanFile(getContext(), newVisibleFile);
+            MediaStore.scanFile(getContext().getContentResolver(), newVisibleFile);
         }
     }
 
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index f5fae19..22fe31e 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -444,7 +444,7 @@
         return sum;
     }
 
-     /**
+    /**
      * Configure the native library files managed by Incremental Service. Makes sure Incremental
      * Service will create native library directories and set up native library binary files in the
      * same structure as they are in non-incremental installations.
@@ -458,8 +458,20 @@
      */
     public static int configureNativeBinariesForSupportedAbi(AndroidPackage pkg, Handle handle,
             File libraryRoot, String[] abiList, boolean useIsaSubdir) {
-        // TODO(b/136132412): Implement this.
-        return -1;
+        int abi = findSupportedAbi(handle, abiList);
+        if (abi < 0) {
+            Slog.e(TAG, "Failed to find find matching ABI.");
+            return abi;
+        }
+
+        // Currently only support installations that have pre-configured native library files
+        // TODO(b/136132412): implement this after incfs supports file mapping
+        if (!libraryRoot.exists()) {
+            Slog.e(TAG, "Incremental installation currently does not configure native libs");
+            return INSTALL_FAILED_NO_MATCHING_ABIS;
+        }
+
+        return abi;
     }
 
     // We don't care about the other return values for now.
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index d78bd73..b250578 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -531,6 +531,12 @@
                     try {
                         AndroidFuture.this.complete((T) resultContainer.get());
                     } catch (Throwable t) {
+                        // If resultContainer was completed exceptionally, get() wraps the
+                        // underlying exception in an ExecutionException. Unwrap it now to avoid
+                        // double-layering ExecutionExceptions.
+                        if (t instanceof ExecutionException && t.getCause() != null) {
+                            t = t.getCause();
+                        }
                         completeExceptionally(t);
                     }
                 }
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 01f5743..cb67309 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -43,7 +43,6 @@
     void onDataConnectionStateChanged(int state, int networkType);
     void onDataActivity(int direction);
     void onSignalStrengthsChanged(in SignalStrength signalStrength);
-    void onOtaspChanged(in int otaspMode);
     void onCellInfoChanged(in List<CellInfo> cellInfo);
     void onPreciseCallStateChanged(in PreciseCallState callState);
     void onPreciseDataConnectionStateChanged(in PreciseDataConnectionState dataConnectionState);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 2f34aa0..f954679 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -24,6 +24,8 @@
 import android.telephony.CellInfo;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.PhoneCapability;
+import android.telephony.PhysicalChannelConfig;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
@@ -60,21 +62,13 @@
     @UnsupportedAppUsage(maxTargetSdk = 28)
     void notifyDataActivity(int state);
     void notifyDataActivityForSubscriber(in int subId, int state);
-    void notifyDataConnection(int state, boolean isDataConnectivityPossible,
-            String apn, String apnType, in LinkProperties linkProperties,
-            in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
-    void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
-            boolean isDataConnectivityPossible,
-            String apn, String apnType, in LinkProperties linkProperties,
-            in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
+    void notifyDataConnectionForSubscriber(
+            int phoneId, int subId, String apnType, in PreciseDataConnectionState preciseState);
     @UnsupportedAppUsage
     void notifyDataConnectionFailed(String apnType);
-    void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType);
     @UnsupportedAppUsage(maxTargetSdk = 28)
     void notifyCellLocation(in Bundle cellLocation);
     void notifyCellLocationForSubscriber(in int subId, in Bundle cellLocation);
-    @UnsupportedAppUsage(maxTargetSdk = 28)
-    void notifyOtaspChanged(in int subId, in int otaspMode);
     @UnsupportedAppUsage
     void notifyCellInfo(in List<CellInfo> cellInfo);
     void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 4165f20..4dac542 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -308,6 +308,17 @@
     }
 
     /**
+     * @see #add(List, Object)
+     */
+    public static @NonNull <K, V> Map<K, V> add(@Nullable Map<K, V> map, K key, V value) {
+        if (map == null || map == Collections.emptyMap()) {
+            map = new ArrayMap<>();
+        }
+        map.put(key, value);
+        return map;
+    }
+
+    /**
      * Similar to {@link List#remove}, but with support for list values of {@code null} and
      * {@link Collections#emptyList}
      */
diff --git a/core/java/com/android/internal/util/ScreenRecordHelper.java b/core/java/com/android/internal/util/ScreenRecordHelper.java
index 64d0898..ec7ed4e 100644
--- a/core/java/com/android/internal/util/ScreenRecordHelper.java
+++ b/core/java/com/android/internal/util/ScreenRecordHelper.java
@@ -24,10 +24,6 @@
  * Helper class to initiate a screen recording
  */
 public class ScreenRecordHelper {
-    private static final String SYSUI_PACKAGE = "com.android.systemui";
-    private static final String SYSUI_SCREENRECORD_LAUNCHER =
-            "com.android.systemui.screenrecord.ScreenRecordDialog";
-
     private final Context mContext;
 
     /**
@@ -42,8 +38,9 @@
      * Show dialog of screen recording options to user.
      */
     public void launchRecordPrompt() {
-        final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
-                SYSUI_SCREENRECORD_LAUNCHER);
+        final ComponentName launcherComponent = ComponentName.unflattenFromString(
+                mContext.getResources().getString(
+                        com.android.internal.R.string.config_screenRecorderComponent));
         final Intent intent = new Intent();
         intent.setComponent(launcherComponent);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index f6f187f..8cad5a0 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -20,12 +20,6 @@
 public class ScreenshotHelper {
     private static final String TAG = "ScreenshotHelper";
 
-    private static final String SYSUI_PACKAGE = "com.android.systemui";
-    private static final String SYSUI_SCREENSHOT_SERVICE =
-            "com.android.systemui.screenshot.TakeScreenshotService";
-    private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER =
-            "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver";
-
     // Time until we give up on the screenshot & show an error instead.
     private final int SCREENSHOT_TIMEOUT_MS = 10000;
 
@@ -94,8 +88,9 @@
             if (mScreenshotConnection != null) {
                 return;
             }
-            final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
-                    SYSUI_SCREENSHOT_SERVICE);
+            final ComponentName serviceComponent = ComponentName.unflattenFromString(
+                    mContext.getResources().getString(
+                            com.android.internal.R.string.config_screenshotServiceComponent));
             final Intent serviceIntent = new Intent();
 
             final Runnable mScreenshotTimeout = new Runnable() {
@@ -181,8 +176,9 @@
      */
     private void notifyScreenshotError() {
         // If the service process is killed, then ask it to clean up after itself
-        final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE,
-                SYSUI_SCREENSHOT_ERROR_RECEIVER);
+        final ComponentName errorComponent = ComponentName.unflattenFromString(
+                mContext.getResources().getString(
+                        com.android.internal.R.string.config_screenshotErrorReceiverComponent));
         // Broadcast needs to have a valid action.  We'll just pick
         // a generic one, since the receiver here doesn't care.
         Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT);
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 2ee902a..58aaa80 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -16,13 +16,16 @@
 
 package com.android.internal.view;
 
+import android.content.ComponentName;
 import android.os.IBinder;
 import android.os.ResultReceiver;
+import android.view.autofill.AutofillId;
 import android.view.InputChannel;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputMethodSubtype;
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodSession;
 import com.android.internal.view.IInputSessionCallback;
@@ -35,6 +38,9 @@
 oneway interface IInputMethod {
     void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privOps);
 
+    void onCreateInlineSuggestionsRequest(in ComponentName componentName, in AutofillId autofillId,
+            in IInlineSuggestionsRequestCallback cb);
+
     void bindInput(in InputBinding binding);
 
     void unbindInput();
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 7562bad..8a59c99 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -25,6 +25,7 @@
 import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.Trace;
@@ -228,7 +229,7 @@
      * Map of system pre-defined, uniquely named actors; keys are namespace,
      * value maps actor name to package name.
      */
-    private ArrayMap<String, ArrayMap<String, String>> mNamedActors = null;
+    private Map<String, Map<String, String>> mNamedActors = null;
 
     public static SystemConfig getInstance() {
         if (!isSystemProcess()) {
@@ -412,7 +413,7 @@
     }
 
     @NonNull
-    public Map<String, ? extends Map<String, String>> getNamedActors() {
+    public Map<String, Map<String, String>> getNamedActors() {
         return mNamedActors != null ? mNamedActors : Collections.emptyMap();
     }
 
@@ -498,6 +499,19 @@
                 Environment.getSystemExtDirectory(), "etc", "sysconfig"), ALLOW_ALL);
         readPermissions(Environment.buildPath(
                 Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL);
+
+        // Skip loading configuration from apex if it is not a system process.
+        if (!isSystemProcess()) {
+            return;
+        }
+        // Read configuration of libs from apex module.
+        // TODO: Use a solid way to filter apex module folders?
+        for (File f: FileUtils.listFilesOrEmpty(Environment.getApexDirectory())) {
+            if (f.isFile() || f.getPath().contains("@")) {
+                continue;
+            }
+            readPermissions(Environment.buildPath(f, "etc", "permissions"), ALLOW_LIBS);
+        }
     }
 
     @VisibleForTesting
@@ -1069,7 +1083,7 @@
                                 mNamedActors = new ArrayMap<>();
                             }
 
-                            ArrayMap<String, String> nameToPkgMap = mNamedActors.get(namespace);
+                            Map<String, String> nameToPkgMap = mNamedActors.get(namespace);
                             if (nameToPkgMap == null) {
                                 nameToPkgMap = new ArrayMap<>();
                                 mNamedActors.put(namespace, nameToPkgMap);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index ea00519..60c5bf1 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -176,7 +176,6 @@
                 "android_hardware_HardwareBuffer.cpp",
                 "android_hardware_SensorManager.cpp",
                 "android_hardware_SerialPort.cpp",
-                "android_hardware_SoundTrigger.cpp",
                 "android_hardware_UsbDevice.cpp",
                 "android_hardware_UsbDeviceConnection.cpp",
                 "android_hardware_UsbRequest.cpp",
@@ -258,7 +257,6 @@
                 "libpdfium",
                 "libimg_utils",
                 "libnetd_client",
-                "libsoundtrigger",
                 "libprocessgroup",
                 "libnativebridge_lazy",
                 "libnativeloader_lazy",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b8fd3ad..3cde887 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -80,7 +80,6 @@
 extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
 extern int register_android_hardware_SensorManager(JNIEnv *env);
 extern int register_android_hardware_SerialPort(JNIEnv *env);
-extern int register_android_hardware_SoundTrigger(JNIEnv *env);
 extern int register_android_hardware_UsbDevice(JNIEnv *env);
 extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
 extern int register_android_hardware_UsbRequest(JNIEnv *env);
@@ -130,6 +129,7 @@
 extern int register_android_database_SQLiteConnection(JNIEnv* env);
 extern int register_android_database_SQLiteGlobal(JNIEnv* env);
 extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_media_MediaMetrics(JNIEnv *env);
 extern int register_android_os_Debug(JNIEnv* env);
 extern int register_android_os_GraphicsEnvironment(JNIEnv* env);
 extern int register_android_os_HidlSupport(JNIEnv* env);
@@ -1508,7 +1508,6 @@
     REG_JNI(register_android_hardware_HardwareBuffer),
     REG_JNI(register_android_hardware_SensorManager),
     REG_JNI(register_android_hardware_SerialPort),
-    REG_JNI(register_android_hardware_SoundTrigger),
     REG_JNI(register_android_hardware_UsbDevice),
     REG_JNI(register_android_hardware_UsbDeviceConnection),
     REG_JNI(register_android_hardware_UsbRequest),
@@ -1522,6 +1521,7 @@
     REG_JNI(register_android_media_AudioProductStrategies),
     REG_JNI(register_android_media_AudioVolumeGroups),
     REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
+    REG_JNI(register_android_media_MediaMetrics),
     REG_JNI(register_android_media_MicrophoneInfo),
     REG_JNI(register_android_media_RemoteDisplay),
     REG_JNI(register_android_media_ToneGenerator),
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
deleted file mode 100644
index 4376b0b..0000000
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ /dev/null
@@ -1,1071 +0,0 @@
-/*
-**
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SoundTrigger-JNI"
-#include <utils/Log.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedUtfChars.h>
-#include "core_jni_helpers.h"
-#include <system/sound_trigger.h>
-#include <soundtrigger/SoundTriggerCallback.h>
-#include <soundtrigger/SoundTrigger.h>
-#include <utils/RefBase.h>
-#include <utils/Vector.h>
-#include <binder/IMemory.h>
-#include <binder/MemoryDealer.h>
-#include "android_media_AudioFormat.h"
-
-using namespace android;
-
-static jclass gArrayListClass;
-static struct {
-    jmethodID    add;
-} gArrayListMethods;
-
-static jclass gUUIDClass;
-static struct {
-    jmethodID    toString;
-} gUUIDMethods;
-
-static const char* const kUnsupportedOperationExceptionClassPathName =
-     "java/lang/UnsupportedOperationException";
-static jclass gUnsupportedOperationExceptionClass;
-static const char* const kIllegalArgumentExceptionClassPathName =
-     "java/lang/IllegalArgumentException";
-static jclass gIllegalArgumentExceptionClass;
-
-static const char* const kSoundTriggerClassPathName = "android/hardware/soundtrigger/SoundTrigger";
-static jclass gSoundTriggerClass;
-
-static const char* const kModuleClassPathName = "android/hardware/soundtrigger/SoundTriggerModule";
-static jclass gModuleClass;
-static struct {
-    jfieldID    mNativeContext;
-    jfieldID    mId;
-} gModuleFields;
-static jmethodID   gPostEventFromNative;
-
-static const char* const kModulePropertiesClassPathName =
-                                     "android/hardware/soundtrigger/SoundTrigger$ModuleProperties";
-static jclass gModulePropertiesClass;
-static jmethodID   gModulePropertiesCstor;
-
-static const char* const kSoundModelClassPathName =
-                                     "android/hardware/soundtrigger/SoundTrigger$SoundModel";
-static jclass gSoundModelClass;
-static struct {
-    jfieldID    uuid;
-    jfieldID    vendorUuid;
-    jfieldID    data;
-} gSoundModelFields;
-
-static const char* const kGenericSoundModelClassPathName =
-                                     "android/hardware/soundtrigger/SoundTrigger$GenericSoundModel";
-static jclass gGenericSoundModelClass;
-
-static const char* const kKeyphraseClassPathName =
-                                     "android/hardware/soundtrigger/SoundTrigger$Keyphrase";
-static jclass gKeyphraseClass;
-static struct {
-    jfieldID id;
-    jfieldID recognitionModes;
-    jfieldID locale;
-    jfieldID text;
-    jfieldID users;
-} gKeyphraseFields;
-
-static const char* const kKeyphraseSoundModelClassPathName =
-                                 "android/hardware/soundtrigger/SoundTrigger$KeyphraseSoundModel";
-static jclass gKeyphraseSoundModelClass;
-static struct {
-    jfieldID    keyphrases;
-} gKeyphraseSoundModelFields;
-
-static const char* const kModelParamRangeClassPathName =
-                                "android/hardware/soundtrigger/SoundTrigger$ModelParamRange";
-static jclass gModelParamRangeClass;
-static jmethodID gModelParamRangeCstor;
-
-static const char* const kRecognitionConfigClassPathName =
-                                     "android/hardware/soundtrigger/SoundTrigger$RecognitionConfig";
-static jclass gRecognitionConfigClass;
-static struct {
-    jfieldID captureRequested;
-    jfieldID keyphrases;
-    jfieldID data;
-} gRecognitionConfigFields;
-
-static const char* const kRecognitionEventClassPathName =
-                                     "android/hardware/soundtrigger/SoundTrigger$RecognitionEvent";
-static jclass gRecognitionEventClass;
-static jmethodID   gRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionEventClassPathName =
-                             "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionEvent";
-static jclass gKeyphraseRecognitionEventClass;
-static jmethodID   gKeyphraseRecognitionEventCstor;
-
-static const char* const kGenericRecognitionEventClassPathName =
-                             "android/hardware/soundtrigger/SoundTrigger$GenericRecognitionEvent";
-static jclass gGenericRecognitionEventClass;
-static jmethodID   gGenericRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionExtraClassPathName =
-                             "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra";
-static jclass gKeyphraseRecognitionExtraClass;
-static jmethodID   gKeyphraseRecognitionExtraCstor;
-static struct {
-    jfieldID id;
-    jfieldID recognitionModes;
-    jfieldID coarseConfidenceLevel;
-    jfieldID confidenceLevels;
-} gKeyphraseRecognitionExtraFields;
-
-static const char* const kConfidenceLevelClassPathName =
-                             "android/hardware/soundtrigger/SoundTrigger$ConfidenceLevel";
-static jclass gConfidenceLevelClass;
-static jmethodID   gConfidenceLevelCstor;
-static struct {
-    jfieldID userId;
-    jfieldID confidenceLevel;
-} gConfidenceLevelFields;
-
-static const char* const kAudioFormatClassPathName =
-                             "android/media/AudioFormat";
-static jclass gAudioFormatClass;
-static jmethodID gAudioFormatCstor;
-
-static const char* const kSoundModelEventClassPathName =
-                                     "android/hardware/soundtrigger/SoundTrigger$SoundModelEvent";
-static jclass gSoundModelEventClass;
-static jmethodID   gSoundModelEventCstor;
-
-static Mutex gLock;
-
-enum {
-    SOUNDTRIGGER_STATUS_OK = 0,
-    SOUNDTRIGGER_STATUS_ERROR = INT_MIN,
-    SOUNDTRIGGER_PERMISSION_DENIED = -1,
-    SOUNDTRIGGER_STATUS_NO_INIT = -19,
-    SOUNDTRIGGER_STATUS_BAD_VALUE = -22,
-    SOUNDTRIGGER_STATUS_DEAD_OBJECT = -32,
-    SOUNDTRIGGER_INVALID_OPERATION = -38,
-};
-
-enum  {
-    SOUNDTRIGGER_EVENT_RECOGNITION = 1,
-    SOUNDTRIGGER_EVENT_SERVICE_DIED = 2,
-    SOUNDTRIGGER_EVENT_SOUNDMODEL = 3,
-    SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE = 4,
-};
-
-static jint throwUnsupportedOperationException(JNIEnv *env)
-{
-    return env->ThrowNew(gUnsupportedOperationExceptionClass, nullptr);
-}
-
-static jint throwIllegalArgumentException(JNIEnv *env)
-{
-    return env->ThrowNew(gIllegalArgumentExceptionClass, nullptr);
-}
-
-// ----------------------------------------------------------------------------
-// ref-counted object for callbacks
-class JNISoundTriggerCallback: public SoundTriggerCallback
-{
-public:
-    JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
-    ~JNISoundTriggerCallback();
-
-    virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event);
-    virtual void onSoundModelEvent(struct sound_trigger_model_event *event);
-    virtual void onServiceStateChange(sound_trigger_service_state_t state);
-    virtual void onServiceDied();
-
-private:
-    jclass      mClass;     // Reference to SoundTrigger class
-    jobject     mObject;    // Weak ref to SoundTrigger Java object to call on
-};
-
-JNISoundTriggerCallback::JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
-{
-
-    // Hold onto the SoundTriggerModule class for use in calling the static method
-    // that posts events to the application thread.
-    jclass clazz = env->GetObjectClass(thiz);
-    if (clazz == NULL) {
-        ALOGE("Can't find class %s", kModuleClassPathName);
-        return;
-    }
-    mClass = (jclass)env->NewGlobalRef(clazz);
-
-    // We use a weak reference so the SoundTriggerModule object can be garbage collected.
-    // The reference is only used as a proxy for callbacks.
-    mObject  = env->NewGlobalRef(weak_thiz);
-}
-
-JNISoundTriggerCallback::~JNISoundTriggerCallback()
-{
-    // remove global references
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-    env->DeleteGlobalRef(mObject);
-    env->DeleteGlobalRef(mClass);
-}
-
-void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event)
-{
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobject jEvent = NULL;
-    jbyteArray jData = NULL;
-
-    if (event->data_size) {
-        jData = env->NewByteArray(event->data_size);
-        jbyte *nData = env->GetByteArrayElements(jData, NULL);
-        memcpy(nData, (char *)event + event->data_offset, event->data_size);
-        env->ReleaseByteArrayElements(jData, nData, 0);
-    }
-
-    jobject jAudioFormat = NULL;
-    if (event->trigger_in_data || event->capture_available) {
-        jint channelMask = (jint)audio_channel_mask_get_bits(event->audio_config.channel_mask);
-        jint channelIndexMask = (jint)AUDIO_CHANNEL_NONE;
-
-        switch (audio_channel_mask_get_representation(event->audio_config.channel_mask)) {
-        case AUDIO_CHANNEL_REPRESENTATION_INDEX:
-            channelIndexMask = channelMask;
-            channelMask = (jint)AUDIO_CHANNEL_NONE;
-            break;
-        default:
-            break;
-        }
-        jAudioFormat = env->NewObject(gAudioFormatClass,
-                                    gAudioFormatCstor,
-                                    audioFormatFromNative(event->audio_config.format),
-                                    event->audio_config.sample_rate,
-                                    channelMask,
-                                    channelIndexMask);
-
-    }
-    if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) {
-        struct sound_trigger_phrase_recognition_event *phraseEvent =
-                (struct sound_trigger_phrase_recognition_event *)event;
-
-        jobjectArray jExtras = env->NewObjectArray(phraseEvent->num_phrases,
-                                                  gKeyphraseRecognitionExtraClass, NULL);
-        if (jExtras == NULL) {
-            return;
-        }
-
-        for (size_t i = 0; i < phraseEvent->num_phrases; i++) {
-            jobjectArray jConfidenceLevels = env->NewObjectArray(
-                                                        phraseEvent->phrase_extras[i].num_levels,
-                                                        gConfidenceLevelClass, NULL);
-
-            if (jConfidenceLevels == NULL) {
-                return;
-            }
-            for (size_t j = 0; j < phraseEvent->phrase_extras[i].num_levels; j++) {
-                jobject jConfidenceLevel = env->NewObject(gConfidenceLevelClass,
-                                                  gConfidenceLevelCstor,
-                                                  phraseEvent->phrase_extras[i].levels[j].user_id,
-                                                  phraseEvent->phrase_extras[i].levels[j].level);
-                env->SetObjectArrayElement(jConfidenceLevels, j, jConfidenceLevel);
-                env->DeleteLocalRef(jConfidenceLevel);
-            }
-
-            jobject jNewExtra = env->NewObject(gKeyphraseRecognitionExtraClass,
-                                               gKeyphraseRecognitionExtraCstor,
-                                               phraseEvent->phrase_extras[i].id,
-                                               phraseEvent->phrase_extras[i].recognition_modes,
-                                               phraseEvent->phrase_extras[i].confidence_level,
-                                               jConfidenceLevels);
-
-            if (jNewExtra == NULL) {
-                return;
-            }
-            env->SetObjectArrayElement(jExtras, i, jNewExtra);
-            env->DeleteLocalRef(jNewExtra);
-            env->DeleteLocalRef(jConfidenceLevels);
-        }
-        jEvent = env->NewObject(gKeyphraseRecognitionEventClass, gKeyphraseRecognitionEventCstor,
-                                event->status, event->model, event->capture_available,
-                                event->capture_session, event->capture_delay_ms,
-                                event->capture_preamble_ms, event->trigger_in_data,
-                                jAudioFormat, jData, jExtras);
-        env->DeleteLocalRef(jExtras);
-    } else if (event->type == SOUND_MODEL_TYPE_GENERIC) {
-        jEvent = env->NewObject(gGenericRecognitionEventClass, gGenericRecognitionEventCstor,
-                                event->status, event->model, event->capture_available,
-                                event->capture_session, event->capture_delay_ms,
-                                event->capture_preamble_ms, event->trigger_in_data,
-                                jAudioFormat, jData);
-    } else {
-        jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor,
-                                event->status, event->model, event->capture_available,
-                                event->capture_session, event->capture_delay_ms,
-                                event->capture_preamble_ms, event->trigger_in_data,
-                                jAudioFormat, jData);
-    }
-
-    if (jAudioFormat != NULL) {
-        env->DeleteLocalRef(jAudioFormat);
-    }
-    if (jData != NULL) {
-        env->DeleteLocalRef(jData);
-    }
-
-    env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
-                              SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent);
-
-    env->DeleteLocalRef(jEvent);
-    if (env->ExceptionCheck()) {
-        ALOGW("An exception occurred while notifying an event.");
-        env->ExceptionClear();
-    }
-}
-
-void JNISoundTriggerCallback::onSoundModelEvent(struct sound_trigger_model_event *event)
-{
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobject jEvent = NULL;
-    jbyteArray jData = NULL;
-
-    if (event->data_size) {
-        jData = env->NewByteArray(event->data_size);
-        jbyte *nData = env->GetByteArrayElements(jData, NULL);
-        memcpy(nData, (char *)event + event->data_offset, event->data_size);
-        env->ReleaseByteArrayElements(jData, nData, 0);
-    }
-
-    jEvent = env->NewObject(gSoundModelEventClass, gSoundModelEventCstor,
-                            event->status, event->model, jData);
-
-    env->DeleteLocalRef(jData);
-    env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
-                              SOUNDTRIGGER_EVENT_SOUNDMODEL, 0, 0, jEvent);
-    env->DeleteLocalRef(jEvent);
-    if (env->ExceptionCheck()) {
-        ALOGW("An exception occurred while notifying an event.");
-        env->ExceptionClear();
-    }
-}
-
-void JNISoundTriggerCallback::onServiceStateChange(sound_trigger_service_state_t state)
-{
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-    env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
-                                        SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE, state, 0, NULL);
-    if (env->ExceptionCheck()) {
-        ALOGW("An exception occurred while notifying an event.");
-        env->ExceptionClear();
-    }
-}
-
-void JNISoundTriggerCallback::onServiceDied()
-{
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-
-    env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
-                              SOUNDTRIGGER_EVENT_SERVICE_DIED, 0, 0, NULL);
-    if (env->ExceptionCheck()) {
-        ALOGW("An exception occurred while notifying an event.");
-        env->ExceptionClear();
-    }
-}
-
-// ----------------------------------------------------------------------------
-
-static sp<SoundTrigger> getSoundTrigger(JNIEnv* env, jobject thiz)
-{
-    Mutex::Autolock l(gLock);
-    SoundTrigger* const st = (SoundTrigger*)env->GetLongField(thiz,
-                                                         gModuleFields.mNativeContext);
-    return sp<SoundTrigger>(st);
-}
-
-static sp<SoundTrigger> setSoundTrigger(JNIEnv* env, jobject thiz, const sp<SoundTrigger>& module)
-{
-    Mutex::Autolock l(gLock);
-    sp<SoundTrigger> old = (SoundTrigger*)env->GetLongField(thiz,
-                                                         gModuleFields.mNativeContext);
-    if (module.get()) {
-        module->incStrong((void*)setSoundTrigger);
-    }
-    if (old != 0) {
-        old->decStrong((void*)setSoundTrigger);
-    }
-    env->SetLongField(thiz, gModuleFields.mNativeContext, (jlong)module.get());
-    return old;
-}
-
-
-static jint
-android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz,
-                                          jstring opPackageName, jobject jModules)
-{
-    ALOGV("listModules");
-
-    if (jModules == NULL) {
-        ALOGE("listModules NULL AudioPatch ArrayList");
-        return SOUNDTRIGGER_STATUS_BAD_VALUE;
-    }
-    if (!env->IsInstanceOf(jModules, gArrayListClass)) {
-        ALOGE("listModules not an arraylist");
-        return SOUNDTRIGGER_STATUS_BAD_VALUE;
-    }
-
-    unsigned int numModules = 0;
-    struct sound_trigger_module_descriptor *nModules = NULL;
-
-    ScopedUtfChars opPackageNameStr(env, opPackageName);
-    const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
-    status_t status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
-    if (status != NO_ERROR || numModules == 0) {
-        return (jint)status;
-    }
-
-    nModules = (struct sound_trigger_module_descriptor *)
-                            calloc(numModules, sizeof(struct sound_trigger_module_descriptor));
-
-    status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
-    ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules);
-
-    if (status != NO_ERROR) {
-        numModules = 0;
-    }
-
-    for (size_t i = 0; i < numModules; i++) {
-        char str[SOUND_TRIGGER_MAX_STRING_LEN];
-
-        jstring implementor = env->NewStringUTF(nModules[i].properties.implementor);
-        jstring description = env->NewStringUTF(nModules[i].properties.description);
-        SoundTrigger::guidToString(&nModules[i].properties.uuid,
-                                   str,
-                                   SOUND_TRIGGER_MAX_STRING_LEN);
-        jstring uuid = env->NewStringUTF(str);
-
-        ALOGV("listModules module %zu id %d description %s maxSoundModels %d",
-              i, nModules[i].handle, nModules[i].properties.description,
-              nModules[i].properties.max_sound_models);
-
-        jobject newModuleDesc = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor,
-                                               nModules[i].handle,
-                                               implementor, description, uuid,
-                                               nModules[i].properties.version,
-                                               nModules[i].properties.max_sound_models,
-                                               nModules[i].properties.max_key_phrases,
-                                               nModules[i].properties.max_users,
-                                               nModules[i].properties.recognition_modes,
-                                               nModules[i].properties.capture_transition,
-                                               nModules[i].properties.max_buffer_ms,
-                                               nModules[i].properties.concurrent_capture,
-                                               nModules[i].properties.power_consumption_mw,
-                                               nModules[i].properties.trigger_in_event);
-
-        env->DeleteLocalRef(implementor);
-        env->DeleteLocalRef(description);
-        env->DeleteLocalRef(uuid);
-        if (newModuleDesc == NULL) {
-            status = SOUNDTRIGGER_STATUS_ERROR;
-            goto exit;
-        }
-        env->CallBooleanMethod(jModules, gArrayListMethods.add, newModuleDesc);
-    }
-
-exit:
-    free(nModules);
-    return (jint) status;
-}
-
-static void
-android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz,
-                                    jstring opPackageName, jobject weak_this)
-{
-    ALOGV("setup");
-
-    ScopedUtfChars opPackageNameStr(env, opPackageName);
-    const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
-    sp<JNISoundTriggerCallback> callback = new JNISoundTriggerCallback(env, thiz, weak_this);
-
-    sound_trigger_module_handle_t handle =
-            (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId);
-
-    sp<SoundTrigger> module = SoundTrigger::attach(opPackageNameString16, handle, callback);
-    if (module == 0) {
-        return;
-    }
-
-    setSoundTrigger(env, thiz, module);
-}
-
-static void
-android_hardware_SoundTrigger_detach(JNIEnv *env, jobject thiz)
-{
-    ALOGV("detach");
-    sp<SoundTrigger> module = setSoundTrigger(env, thiz, 0);
-    ALOGV("detach module %p", module.get());
-    if (module != 0) {
-        ALOGV("detach module->detach()");
-        module->detach();
-    }
-}
-
-static void
-android_hardware_SoundTrigger_finalize(JNIEnv *env, jobject thiz)
-{
-    ALOGV("finalize");
-    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
-    if (module != 0) {
-        ALOGW("SoundTrigger finalized without being detached");
-    }
-    android_hardware_SoundTrigger_detach(env, thiz);
-}
-
-static jint
-android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
-                                             jobject jSoundModel, jintArray jHandle)
-{
-    jint status = SOUNDTRIGGER_STATUS_OK;
-    jbyte *nData = NULL;
-    struct sound_trigger_sound_model *nSoundModel;
-    jbyteArray jData;
-    sp<MemoryDealer> memoryDealer;
-    sp<IMemory> memory;
-    size_t size;
-    sound_model_handle_t handle = 0;
-    jobject jUuid;
-    jstring jUuidString;
-    const char *nUuidString;
-
-    ALOGV("loadSoundModel");
-    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
-    if (module == NULL) {
-        return SOUNDTRIGGER_STATUS_ERROR;
-    }
-    if (jHandle == NULL) {
-        return SOUNDTRIGGER_STATUS_BAD_VALUE;
-    }
-    jsize jHandleLen = env->GetArrayLength(jHandle);
-    if (jHandleLen == 0) {
-        return SOUNDTRIGGER_STATUS_BAD_VALUE;
-    }
-    jint *nHandle = env->GetIntArrayElements(jHandle, NULL);
-    if (nHandle == NULL) {
-        return SOUNDTRIGGER_STATUS_ERROR;
-    }
-    if (!env->IsInstanceOf(jSoundModel, gSoundModelClass)) {
-        status = SOUNDTRIGGER_STATUS_BAD_VALUE;
-        goto exit;
-    }
-    size_t offset;
-    sound_trigger_sound_model_type_t type;
-    if (env->IsInstanceOf(jSoundModel, gKeyphraseSoundModelClass)) {
-        offset = sizeof(struct sound_trigger_phrase_sound_model);
-        type = SOUND_MODEL_TYPE_KEYPHRASE;
-    } else if (env->IsInstanceOf(jSoundModel, gGenericSoundModelClass)) {
-        offset = sizeof(struct sound_trigger_generic_sound_model);
-        type = SOUND_MODEL_TYPE_GENERIC;
-    } else {
-        offset = sizeof(struct sound_trigger_sound_model);
-        type = SOUND_MODEL_TYPE_UNKNOWN;
-    }
-
-    jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.uuid);
-    jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
-    nUuidString = env->GetStringUTFChars(jUuidString, NULL);
-    sound_trigger_uuid_t nUuid;
-    SoundTrigger::stringToGuid(nUuidString, &nUuid);
-    env->ReleaseStringUTFChars(jUuidString, nUuidString);
-    env->DeleteLocalRef(jUuidString);
-
-    sound_trigger_uuid_t nVendorUuid;
-    jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.vendorUuid);
-    if (jUuid != NULL) {
-        jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
-        nUuidString = env->GetStringUTFChars(jUuidString, NULL);
-        SoundTrigger::stringToGuid(nUuidString, &nVendorUuid);
-        env->ReleaseStringUTFChars(jUuidString, nUuidString);
-        env->DeleteLocalRef(jUuidString);
-    } else {
-        SoundTrigger::stringToGuid("00000000-0000-0000-0000-000000000000", &nVendorUuid);
-    }
-
-    jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data);
-    if (jData == NULL) {
-        status = SOUNDTRIGGER_STATUS_BAD_VALUE;
-        goto exit;
-    }
-    size = env->GetArrayLength(jData);
-
-    nData = env->GetByteArrayElements(jData, NULL);
-    if (jData == NULL) {
-        status = SOUNDTRIGGER_STATUS_ERROR;
-        goto exit;
-    }
-
-    memoryDealer = new MemoryDealer(offset + size, "SoundTrigge-JNI::LoadModel");
-    if (memoryDealer == 0) {
-        status = SOUNDTRIGGER_STATUS_ERROR;
-        goto exit;
-    }
-    memory = memoryDealer->allocate(offset + size);
-    if (memory == 0 || memory->unsecurePointer() == NULL) {
-        status = SOUNDTRIGGER_STATUS_ERROR;
-        goto exit;
-    }
-
-    nSoundModel = (struct sound_trigger_sound_model *)memory->unsecurePointer();
-
-    nSoundModel->type = type;
-    nSoundModel->uuid = nUuid;
-    nSoundModel->vendor_uuid = nVendorUuid;
-    nSoundModel->data_size = size;
-    nSoundModel->data_offset = offset;
-    memcpy((char *)nSoundModel + offset, nData, size);
-    if (type == SOUND_MODEL_TYPE_KEYPHRASE) {
-        struct sound_trigger_phrase_sound_model *phraseModel =
-                (struct sound_trigger_phrase_sound_model *)nSoundModel;
-
-        jobjectArray jPhrases =
-            (jobjectArray)env->GetObjectField(jSoundModel, gKeyphraseSoundModelFields.keyphrases);
-        if (jPhrases == NULL) {
-            status = SOUNDTRIGGER_STATUS_BAD_VALUE;
-            goto exit;
-        }
-
-        size_t numPhrases = env->GetArrayLength(jPhrases);
-        phraseModel->num_phrases = numPhrases;
-        ALOGV("loadSoundModel numPhrases %zu", numPhrases);
-        for (size_t i = 0; i < numPhrases; i++) {
-            jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
-            phraseModel->phrases[i].id =
-                                    env->GetIntField(jPhrase,gKeyphraseFields.id);
-            phraseModel->phrases[i].recognition_mode =
-                                    env->GetIntField(jPhrase,gKeyphraseFields.recognitionModes);
-
-            jintArray jUsers = (jintArray)env->GetObjectField(jPhrase, gKeyphraseFields.users);
-            phraseModel->phrases[i].num_users = env->GetArrayLength(jUsers);
-            jint *nUsers = env->GetIntArrayElements(jUsers, NULL);
-            memcpy(phraseModel->phrases[i].users,
-                   nUsers,
-                   phraseModel->phrases[i].num_users * sizeof(int));
-            env->ReleaseIntArrayElements(jUsers, nUsers, 0);
-            env->DeleteLocalRef(jUsers);
-
-            jstring jLocale = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.locale);
-            const char *nLocale = env->GetStringUTFChars(jLocale, NULL);
-            strncpy(phraseModel->phrases[i].locale,
-                    nLocale,
-                    SOUND_TRIGGER_MAX_LOCALE_LEN);
-            jstring jText = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.text);
-            const char *nText = env->GetStringUTFChars(jText, NULL);
-            strncpy(phraseModel->phrases[i].text,
-                    nText,
-                    SOUND_TRIGGER_MAX_STRING_LEN);
-
-            env->ReleaseStringUTFChars(jLocale, nLocale);
-            env->DeleteLocalRef(jLocale);
-            env->ReleaseStringUTFChars(jText, nText);
-            env->DeleteLocalRef(jText);
-            ALOGV("loadSoundModel phrases %zu text %s locale %s",
-                  i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale);
-            env->DeleteLocalRef(jPhrase);
-        }
-        env->DeleteLocalRef(jPhrases);
-    } else if (type == SOUND_MODEL_TYPE_GENERIC) {
-        /* No initialization needed */
-    }
-    status = module->loadSoundModel(memory, &handle);
-    ALOGV("loadSoundModel status %d handle %d", status, handle);
-
-exit:
-    if (nHandle != NULL) {
-        nHandle[0] = (jint)handle;
-        env->ReleaseIntArrayElements(jHandle, nHandle, NULL);
-    }
-    if (nData != NULL) {
-        env->ReleaseByteArrayElements(jData, nData, NULL);
-    }
-    return status;
-}
-
-static jint
-android_hardware_SoundTrigger_unloadSoundModel(JNIEnv *env, jobject thiz,
-                                               jint jHandle)
-{
-    jint status = SOUNDTRIGGER_STATUS_OK;
-    ALOGV("unloadSoundModel");
-    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
-    if (module == NULL) {
-        return SOUNDTRIGGER_STATUS_ERROR;
-    }
-    status = module->unloadSoundModel((sound_model_handle_t)jHandle);
-
-    return status;
-}
-
-static jint
-android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz,
-                                               jint jHandle, jobject jConfig)
-{
-    jint status = SOUNDTRIGGER_STATUS_OK;
-    ALOGV("startRecognition");
-    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
-    if (module == NULL) {
-        return SOUNDTRIGGER_STATUS_ERROR;
-    }
-
-    if (!env->IsInstanceOf(jConfig, gRecognitionConfigClass)) {
-        return SOUNDTRIGGER_STATUS_BAD_VALUE;
-    }
-
-    jbyteArray jData = (jbyteArray)env->GetObjectField(jConfig, gRecognitionConfigFields.data);
-    jsize dataSize = 0;
-    jbyte *nData = NULL;
-    if (jData != NULL) {
-        dataSize = env->GetArrayLength(jData);
-        if (dataSize == 0) {
-            return SOUNDTRIGGER_STATUS_BAD_VALUE;
-        }
-        nData = env->GetByteArrayElements(jData, NULL);
-        if (nData == NULL) {
-            return SOUNDTRIGGER_STATUS_ERROR;
-        }
-    }
-
-    size_t totalSize = sizeof(struct sound_trigger_recognition_config) + dataSize;
-    sp<MemoryDealer> memoryDealer =
-            new MemoryDealer(totalSize, "SoundTrigge-JNI::StartRecognition");
-    if (memoryDealer == 0) {
-        return SOUNDTRIGGER_STATUS_ERROR;
-    }
-    sp<IMemory> memory = memoryDealer->allocate(totalSize);
-    if (memory == 0 || memory->unsecurePointer() == NULL) {
-        return SOUNDTRIGGER_STATUS_ERROR;
-    }
-    if (dataSize != 0) {
-        memcpy((char *)memory->unsecurePointer() + sizeof(struct sound_trigger_recognition_config),
-                nData,
-                dataSize);
-        env->ReleaseByteArrayElements(jData, nData, 0);
-    }
-    env->DeleteLocalRef(jData);
-    struct sound_trigger_recognition_config *config =
-                                    (struct sound_trigger_recognition_config *)memory->unsecurePointer();
-    config->data_size = dataSize;
-    config->data_offset = sizeof(struct sound_trigger_recognition_config);
-    config->capture_requested = env->GetBooleanField(jConfig,
-                                                 gRecognitionConfigFields.captureRequested);
-
-    config->num_phrases = 0;
-    jobjectArray jPhrases =
-        (jobjectArray)env->GetObjectField(jConfig, gRecognitionConfigFields.keyphrases);
-    if (jPhrases != NULL) {
-        config->num_phrases = env->GetArrayLength(jPhrases);
-    }
-    ALOGV("startRecognition num phrases %d", config->num_phrases);
-    for (size_t i = 0; i < config->num_phrases; i++) {
-        jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
-        config->phrases[i].id = env->GetIntField(jPhrase,
-                                                gKeyphraseRecognitionExtraFields.id);
-        config->phrases[i].recognition_modes = env->GetIntField(jPhrase,
-                                                gKeyphraseRecognitionExtraFields.recognitionModes);
-        config->phrases[i].confidence_level = env->GetIntField(jPhrase,
-                                            gKeyphraseRecognitionExtraFields.coarseConfidenceLevel);
-        config->phrases[i].num_levels = 0;
-        jobjectArray jConfidenceLevels = (jobjectArray)env->GetObjectField(jPhrase,
-                                                gKeyphraseRecognitionExtraFields.confidenceLevels);
-        if (jConfidenceLevels != NULL) {
-            config->phrases[i].num_levels = env->GetArrayLength(jConfidenceLevels);
-        }
-        ALOGV("startRecognition phrase %zu num_levels %d", i, config->phrases[i].num_levels);
-        for (size_t j = 0; j < config->phrases[i].num_levels; j++) {
-            jobject jConfidenceLevel = env->GetObjectArrayElement(jConfidenceLevels, j);
-            config->phrases[i].levels[j].user_id = env->GetIntField(jConfidenceLevel,
-                                                                    gConfidenceLevelFields.userId);
-            config->phrases[i].levels[j].level = env->GetIntField(jConfidenceLevel,
-                                                          gConfidenceLevelFields.confidenceLevel);
-            env->DeleteLocalRef(jConfidenceLevel);
-        }
-        ALOGV("startRecognition phrases %zu", i);
-        env->DeleteLocalRef(jConfidenceLevels);
-        env->DeleteLocalRef(jPhrase);
-    }
-    env->DeleteLocalRef(jPhrases);
-
-    status = module->startRecognition(jHandle, memory);
-    return status;
-}
-
-static jint
-android_hardware_SoundTrigger_stopRecognition(JNIEnv *env, jobject thiz,
-                                               jint jHandle)
-{
-    jint status = SOUNDTRIGGER_STATUS_OK;
-    ALOGV("stopRecognition");
-    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
-    if (module == NULL) {
-        return SOUNDTRIGGER_STATUS_ERROR;
-    }
-    status = module->stopRecognition(jHandle);
-    return status;
-}
-
-static jint
-android_hardware_SoundTrigger_getModelState(JNIEnv *env, jobject thiz,
-                                            jint jHandle)
-{
-    jint status = SOUNDTRIGGER_STATUS_OK;
-    ALOGV("getModelState");
-    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
-    if (module == NULL) {
-        return SOUNDTRIGGER_STATUS_ERROR;
-    }
-    status = module->getModelState(jHandle);
-    return status;
-}
-
-static jint
-android_hardware_SoundTrigger_setParameter(JNIEnv *env, jobject thiz,
-                                            jint jHandle, jint jModelParam, jint jValue)
-{
-    ALOGV("setParameter");
-    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
-    if (module == NULL) {
-        return SOUNDTRIGGER_STATUS_NO_INIT;
-    }
-    return module->setParameter(jHandle, (sound_trigger_model_parameter_t) jModelParam, jValue);
-}
-
-static jint
-android_hardware_SoundTrigger_getParameter(JNIEnv *env, jobject thiz,
-                                            jint jHandle, jint jModelParam)
-{
-    ALOGV("getParameter");
-    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
-    if (module == NULL) {
-        throwUnsupportedOperationException(env);
-        return -1;
-    }
-
-    jint nValue;
-    jint status = module->getParameter(jHandle,
-            (sound_trigger_model_parameter_t) jModelParam, &nValue);
-
-    switch (status) {
-        case 0:
-            return nValue;
-        case -EINVAL:
-            throwIllegalArgumentException(env);
-            break;
-        default:
-            throwUnsupportedOperationException(env);
-            break;
-    }
-
-    return -1;
-}
-
-static jobject
-android_hardware_SoundTrigger_queryParameter(JNIEnv *env, jobject thiz,
-                                            jint jHandle, jint jModelParam)
-{
-    ALOGV("queryParameter");
-    sp<SoundTrigger> module = getSoundTrigger(env, thiz);
-    if (module == nullptr) {
-        return nullptr;
-    }
-
-    sound_trigger_model_parameter_range_t nRange;
-    jint nValue = module->queryParameter(jHandle,
-            (sound_trigger_model_parameter_t) jModelParam, &nRange);
-
-    if (nValue != 0) {
-        ALOGE("failed to query parameter error code: %d", nValue);
-        return nullptr;
-    }
-
-    return env->NewObject(gModelParamRangeClass, gModelParamRangeCstor, nRange.start, nRange.end);
-}
-
-static const JNINativeMethod gMethods[] = {
-    {"listModules",
-        "(Ljava/lang/String;Ljava/util/ArrayList;)I",
-        (void *)android_hardware_SoundTrigger_listModules},
-};
-
-
-static const JNINativeMethod gModuleMethods[] = {
-    {"native_setup",
-        "(Ljava/lang/String;Ljava/lang/Object;)V",
-        (void *)android_hardware_SoundTrigger_setup},
-    {"native_finalize",
-        "()V",
-        (void *)android_hardware_SoundTrigger_finalize},
-    {"detach",
-        "()V",
-        (void *)android_hardware_SoundTrigger_detach},
-    {"loadSoundModel",
-        "(Landroid/hardware/soundtrigger/SoundTrigger$SoundModel;[I)I",
-        (void *)android_hardware_SoundTrigger_loadSoundModel},
-    {"unloadSoundModel",
-        "(I)I",
-        (void *)android_hardware_SoundTrigger_unloadSoundModel},
-    {"startRecognition",
-        "(ILandroid/hardware/soundtrigger/SoundTrigger$RecognitionConfig;)I",
-        (void *)android_hardware_SoundTrigger_startRecognition},
-    {"stopRecognition",
-        "(I)I",
-        (void *)android_hardware_SoundTrigger_stopRecognition},
-    {"getModelState",
-        "(I)I",
-        (void *)android_hardware_SoundTrigger_getModelState},
-    {"setParameter",
-         "(III)I",
-         (void *)android_hardware_SoundTrigger_setParameter},
-    {"getParameter",
-         "(II)I",
-         (void *)android_hardware_SoundTrigger_getParameter},
-    {"queryParameter",
-         "(II)Landroid/hardware/soundtrigger/SoundTrigger$ModelParamRange;",
-         (void *)android_hardware_SoundTrigger_queryParameter}
-};
-
-int register_android_hardware_SoundTrigger(JNIEnv *env)
-{
-    jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
-    gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass);
-    gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
-
-    jclass uuidClass = FindClassOrDie(env, "java/util/UUID");
-    gUUIDClass = MakeGlobalRefOrDie(env, uuidClass);
-    gUUIDMethods.toString = GetMethodIDOrDie(env, uuidClass, "toString", "()Ljava/lang/String;");
-
-    jclass exUClass = FindClassOrDie(env, kUnsupportedOperationExceptionClassPathName);
-    gUnsupportedOperationExceptionClass = MakeGlobalRefOrDie(env, exUClass);
-
-    jclass exIClass = FindClassOrDie(env, kIllegalArgumentExceptionClassPathName);
-    gIllegalArgumentExceptionClass = MakeGlobalRefOrDie(env, exIClass);
-
-    jclass lClass = FindClassOrDie(env, kSoundTriggerClassPathName);
-    gSoundTriggerClass = MakeGlobalRefOrDie(env, lClass);
-
-    jclass moduleClass = FindClassOrDie(env, kModuleClassPathName);
-    gModuleClass = MakeGlobalRefOrDie(env, moduleClass);
-    gPostEventFromNative = GetStaticMethodIDOrDie(env, moduleClass, "postEventFromNative",
-                                                  "(Ljava/lang/Object;IIILjava/lang/Object;)V");
-    gModuleFields.mNativeContext = GetFieldIDOrDie(env, moduleClass, "mNativeContext", "J");
-    gModuleFields.mId = GetFieldIDOrDie(env, moduleClass, "mId", "I");
-
-    jclass modulePropertiesClass = FindClassOrDie(env, kModulePropertiesClassPathName);
-    gModulePropertiesClass = MakeGlobalRefOrDie(env, modulePropertiesClass);
-    gModulePropertiesCstor = GetMethodIDOrDie(env, modulePropertiesClass, "<init>",
-            "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZIZ)V");
-
-    jclass soundModelClass = FindClassOrDie(env, kSoundModelClassPathName);
-    gSoundModelClass = MakeGlobalRefOrDie(env, soundModelClass);
-    gSoundModelFields.uuid = GetFieldIDOrDie(env, soundModelClass, "uuid", "Ljava/util/UUID;");
-    gSoundModelFields.vendorUuid = GetFieldIDOrDie(env, soundModelClass, "vendorUuid",
-                                                   "Ljava/util/UUID;");
-    gSoundModelFields.data = GetFieldIDOrDie(env, soundModelClass, "data", "[B");
-
-    jclass genericSoundModelClass = FindClassOrDie(env, kGenericSoundModelClassPathName);
-    gGenericSoundModelClass = MakeGlobalRefOrDie(env, genericSoundModelClass);
-
-    jclass keyphraseClass = FindClassOrDie(env, kKeyphraseClassPathName);
-    gKeyphraseClass = MakeGlobalRefOrDie(env, keyphraseClass);
-    gKeyphraseFields.id = GetFieldIDOrDie(env, keyphraseClass, "id", "I");
-    gKeyphraseFields.recognitionModes = GetFieldIDOrDie(env, keyphraseClass, "recognitionModes",
-                                                        "I");
-    gKeyphraseFields.locale = GetFieldIDOrDie(env, keyphraseClass, "locale", "Ljava/lang/String;");
-    gKeyphraseFields.text = GetFieldIDOrDie(env, keyphraseClass, "text", "Ljava/lang/String;");
-    gKeyphraseFields.users = GetFieldIDOrDie(env, keyphraseClass, "users", "[I");
-
-    jclass keyphraseSoundModelClass = FindClassOrDie(env, kKeyphraseSoundModelClassPathName);
-    gKeyphraseSoundModelClass = MakeGlobalRefOrDie(env, keyphraseSoundModelClass);
-    gKeyphraseSoundModelFields.keyphrases = GetFieldIDOrDie(env, keyphraseSoundModelClass,
-                                         "keyphrases",
-                                         "[Landroid/hardware/soundtrigger/SoundTrigger$Keyphrase;");
-
-    jclass modelParamRangeClass = FindClassOrDie(env, kModelParamRangeClassPathName);
-    gModelParamRangeClass = MakeGlobalRefOrDie(env, modelParamRangeClass);
-    gModelParamRangeCstor = GetMethodIDOrDie(env, modelParamRangeClass, "<init>", "(II)V");
-
-    jclass recognitionEventClass = FindClassOrDie(env, kRecognitionEventClassPathName);
-    gRecognitionEventClass = MakeGlobalRefOrDie(env, recognitionEventClass);
-    gRecognitionEventCstor = GetMethodIDOrDie(env, recognitionEventClass, "<init>",
-                                              "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
-    jclass keyphraseRecognitionEventClass = FindClassOrDie(env,
-                                                           kKeyphraseRecognitionEventClassPathName);
-    gKeyphraseRecognitionEventClass = MakeGlobalRefOrDie(env, keyphraseRecognitionEventClass);
-    gKeyphraseRecognitionEventCstor = GetMethodIDOrDie(env, keyphraseRecognitionEventClass, "<init>",
-              "(IIZIIIZLandroid/media/AudioFormat;[B[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
-
-    jclass genericRecognitionEventClass = FindClassOrDie(env,
-                                                           kGenericRecognitionEventClassPathName);
-    gGenericRecognitionEventClass = MakeGlobalRefOrDie(env, genericRecognitionEventClass);
-    gGenericRecognitionEventCstor = GetMethodIDOrDie(env, genericRecognitionEventClass, "<init>",
-                                              "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
-    jclass keyRecognitionConfigClass = FindClassOrDie(env, kRecognitionConfigClassPathName);
-    gRecognitionConfigClass = MakeGlobalRefOrDie(env, keyRecognitionConfigClass);
-    gRecognitionConfigFields.captureRequested = GetFieldIDOrDie(env, keyRecognitionConfigClass,
-                                                                "captureRequested", "Z");
-    gRecognitionConfigFields.keyphrases = GetFieldIDOrDie(env, keyRecognitionConfigClass,
-           "keyphrases", "[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;");
-    gRecognitionConfigFields.data = GetFieldIDOrDie(env, keyRecognitionConfigClass, "data", "[B");
-
-    jclass keyphraseRecognitionExtraClass = FindClassOrDie(env,
-                                                           kKeyphraseRecognitionExtraClassPathName);
-    gKeyphraseRecognitionExtraClass = MakeGlobalRefOrDie(env, keyphraseRecognitionExtraClass);
-    gKeyphraseRecognitionExtraCstor = GetMethodIDOrDie(env, keyphraseRecognitionExtraClass,
-            "<init>", "(III[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;)V");
-    gKeyphraseRecognitionExtraFields.id = GetFieldIDOrDie(env, gKeyphraseRecognitionExtraClass,
-                                                          "id", "I");
-    gKeyphraseRecognitionExtraFields.recognitionModes = GetFieldIDOrDie(env,
-            gKeyphraseRecognitionExtraClass, "recognitionModes", "I");
-    gKeyphraseRecognitionExtraFields.coarseConfidenceLevel = GetFieldIDOrDie(env,
-            gKeyphraseRecognitionExtraClass, "coarseConfidenceLevel", "I");
-    gKeyphraseRecognitionExtraFields.confidenceLevels = GetFieldIDOrDie(env,
-            gKeyphraseRecognitionExtraClass, "confidenceLevels",
-            "[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;");
-
-    jclass confidenceLevelClass = FindClassOrDie(env, kConfidenceLevelClassPathName);
-    gConfidenceLevelClass = MakeGlobalRefOrDie(env, confidenceLevelClass);
-    gConfidenceLevelCstor = GetMethodIDOrDie(env, confidenceLevelClass, "<init>", "(II)V");
-    gConfidenceLevelFields.userId = GetFieldIDOrDie(env, confidenceLevelClass, "userId", "I");
-    gConfidenceLevelFields.confidenceLevel = GetFieldIDOrDie(env, confidenceLevelClass,
-                                                             "confidenceLevel", "I");
-
-    jclass audioFormatClass = FindClassOrDie(env, kAudioFormatClassPathName);
-    gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
-    gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "<init>", "(IIII)V");
-
-    jclass soundModelEventClass = FindClassOrDie(env, kSoundModelEventClassPathName);
-    gSoundModelEventClass = MakeGlobalRefOrDie(env, soundModelEventClass);
-    gSoundModelEventCstor = GetMethodIDOrDie(env, soundModelEventClass, "<init>", "(II[B)V");
-
-
-    RegisterMethodsOrDie(env, kSoundTriggerClassPathName, gMethods, NELEM(gMethods));
-    return RegisterMethodsOrDie(env, kModuleClassPathName, gModuleMethods, NELEM(gModuleMethods));
-}
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 342aba0..6cbc587 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -763,7 +763,7 @@
     }
 
     // get what we have for the metrics from the record session
-    MediaAnalyticsItem *item = NULL;
+    mediametrics::Item *item = NULL;
 
     status_t err = lpRecord->getMetrics(item);
     if (err != OK) {
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index c5049ec..c979133 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -1032,7 +1032,7 @@
     }
 
     // get what we have for the metrics from the track
-    MediaAnalyticsItem *item = NULL;
+    mediametrics::Item *item = NULL;
 
     status_t err = lpTrack->getMetrics(item);
     if (err != OK) {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index f28c422..0ca0dc8 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -74,6 +74,7 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <bionic/malloc.h>
+#include <bionic/page.h>
 #include <cutils/fs.h>
 #include <cutils/multiuser.h>
 #include <cutils/sockets.h>
@@ -714,8 +715,16 @@
   CreateDir(user_source, 0751, AID_ROOT, AID_ROOT, fail_fn);
 
   if (isFuse) {
-    BindMount(mount_mode == MOUNT_EXTERNAL_PASS_THROUGH ? pass_through_source : user_source,
-              "/storage", fail_fn);
+    if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH || mount_mode ==
+        MOUNT_EXTERNAL_INSTALLER || mount_mode == MOUNT_EXTERNAL_FULL) {
+      // For now, MediaProvider, installers and "full" get the pass_through mount
+      // view, which is currently identical to the sdcardfs write view.
+      //
+      // TODO(b/146189163): scope down MOUNT_EXTERNAL_INSTALLER
+      BindMount(pass_through_source, "/storage", fail_fn);
+    } else {
+      BindMount(user_source, "/storage", fail_fn);
+    }
   } else {
     const std::string& storage_source = ExternalStorageViews[mount_mode];
     BindMount(storage_source, "/storage", fail_fn);
@@ -1389,9 +1398,14 @@
                               void* data [[maybe_unused]]) {
   // Search for any execute-only segments and mark them read+execute.
   for (int i = 0; i < info->dlpi_phnum; i++) {
-    if ((info->dlpi_phdr[i].p_type == PT_LOAD) && (info->dlpi_phdr[i].p_flags == PF_X)) {
-      mprotect(reinterpret_cast<void*>(info->dlpi_addr + info->dlpi_phdr[i].p_vaddr),
-               info->dlpi_phdr[i].p_memsz, PROT_READ | PROT_EXEC);
+    const auto& phdr = info->dlpi_phdr[i];
+    if ((phdr.p_type == PT_LOAD) && (phdr.p_flags == PF_X)) {
+      auto addr = reinterpret_cast<void*>(info->dlpi_addr + PAGE_START(phdr.p_vaddr));
+      size_t len = PAGE_OFFSET(phdr.p_vaddr) + phdr.p_memsz;
+      if (mprotect(addr, len, PROT_READ | PROT_EXEC) == -1) {
+        ALOGE("mprotect(%p, %zu, PROT_READ | PROT_EXEC) failed: %m", addr, len);
+        return -1;
+      }
     }
   }
   // Return non-zero to exit dl_iterate_phdr.
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 0c21076..8c1ecae 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -33,12 +33,15 @@
 
 // Static whitelist of open paths that the zygote is allowed to keep open.
 static const char* kPathWhitelist[] = {
+  "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
   "/apex/com.android.conscrypt/javalib/conscrypt.jar",
   "/apex/com.android.ipsec/javalib/ike.jar",
   "/apex/com.android.media/javalib/updatable-media.jar",
+  "/apex/com.android.os.statsd/javalib/framework-statsd.jar",
   "/apex/com.android.sdkext/javalib/framework-sdkext.jar",
   "/apex/com.android.telephony/javalib/telephony-common.jar",
   "/apex/com.android.telephony/javalib/ims-common.jar",
+  "/apex/com.android.wifi/javalib/framework-wifi.jar",
   "/dev/null",
   "/dev/socket/zygote",
   "/dev/socket/zygote_secondary",
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index ab97fdd..44581af 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2467,4 +2467,41 @@
     // CATEGORY: SETTINGS
     // OS: R
     ACCOUNT_WORK = 1807;
+
+    // OPEN: Settings > Developer Options > Bug report handler
+    // CATEGORY: SETTINGS
+    // OS: R
+    SETTINGS_BUGREPORT_HANDLER = 1808;
+
+    // Panel for adding Wi-Fi networks
+    // CATEGORY: SETTINGS
+    // OS: R
+    PANEL_ADD_WIFI_NETWORKS = 1809;
+
+    // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog
+    // CATEGORY: SETTINGS
+    // OS: R
+    DIALOG_TOGGLE_SCREEN_ACCESSIBILITY_BUTTON = 1810;
+
+    // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog in
+    // gesture mode
+    // CATEGORY: SETTINGS
+    // OS: R
+    DIALOG_TOGGLE_SCREEN_GESTURE_NAVIGATION = 1811;
+
+    // OPEN: Settings > Accessibility > Edit shortcut dialog
+    // CATEGORY: SETTINGS
+    // OS: R
+    DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT = 1812;
+
+    // OPEN: Settings > Accessibility > Magnification > Edit shortcut dialog
+    // CATEGORY: SETTINGS
+    // OS: R
+    DIALOG_MAGNIFICATION_EDIT_SHORTCUT = 1813;
+
+    // OPEN: Settings > Accessibility > Color correction > Edit shortcut dialog
+    // CATEGORY: SETTINGS
+    // OS: R
+    DIALOG_DALTONIZER_EDIT_SHORTCUT = 1814;
+
 }
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4ea574d..6a1ec6c 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -219,6 +219,12 @@
     }
     optional Gesture gesture = 74;
 
+    message GestureNavigation {
+        optional SettingProto back_gesture_inset_scale_left = 1 [(android.privacy).dest = DEST_AUTOMATIC];
+        optional SettingProto back_gesture_inset_scale_right = 2 [(android.privacy).dest = DEST_AUTOMATIC];
+    }
+    optional GestureNavigation gesture_navigation = 77;
+
     optional SettingProto immersive_mode_confirmations = 24 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     message Incall {
@@ -316,6 +322,7 @@
     optional SettingProto multi_press_timeout = 38 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
     optional SettingProto navigation_mode = 76 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
     message NfcPayment {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
 
@@ -564,5 +571,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 77;
+    // Next tag = 78;
 }
diff --git a/core/proto/android/stats/docsui/docsui_enums.proto b/core/proto/android/stats/docsui/docsui_enums.proto
index f648912..5963f6a 100644
--- a/core/proto/android/stats/docsui/docsui_enums.proto
+++ b/core/proto/android/stats/docsui/docsui_enums.proto
@@ -56,6 +56,7 @@
     ROOT_VIDEOS = 9;
     ROOT_MTP = 10;
     ROOT_THIRD_PARTY_APP = 11;
+    ROOT_DOCUMENTS = 12;
 }
 
 enum ContextScope {
diff --git a/core/proto/android/util/quotatracker.proto b/core/proto/android/util/quotatracker.proto
new file mode 100644
index 0000000..0dea853
--- /dev/null
+++ b/core/proto/android/util/quotatracker.proto
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.util.quota;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+// A com.android.util.quota.QuotaTracker object.
+message QuotaTrackerProto {
+  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  optional bool is_enabled = 1;
+
+  // If quota is free for everything in the tracker.
+  optional bool is_global_quota_free = 2;
+
+  // Current elapsed realtime.
+  optional int64 elapsed_realtime = 3;
+
+  message AlarmListener {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // Whether the listener is waiting for an alarm or not.
+    optional bool is_waiting = 1;
+    // The time at which the alarm should go off, in the elapsed realtime timebase. Only
+    // valid if is_waiting is true.
+    optional int64 trigger_time_elapsed = 2;
+  }
+
+  // Next tag: 4
+}
+
+// A com.android.util.quota.Category object.
+message CategoryProto {
+  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  // Name of the category set by the system service.
+  optional string name = 1;
+}
+
+// A com.android.util.quota.Uptc object.
+message UptcProto {
+  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  // UserHandle value. Should be 0, 10, 11, 12, etc. where 0 is the owner.
+  optional int32 user_id = 1;
+  // Package name
+  optional string name = 2;
+  // Tag set by the system service to differentiate calls.
+  optional string tag = 3;
+}
+
+// A com.android.util.quota.CountQuotaTracker object.
+message CountQuotaTrackerProto {
+  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  optional QuotaTrackerProto base_quota_data = 1;
+
+  message CountLimit {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional CategoryProto category = 1;
+    optional int32 limit = 2;
+    optional int64 window_size_ms = 3;
+  }
+  repeated CountLimit count_limit = 2;
+
+  message Event {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // The time the event occurred, in the elapsed realtime timebase.
+    optional int64 timestamp_elapsed = 1;
+  }
+
+  message ExecutionStats {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // The time after which this record should be considered invalid (out of date), in the
+    // elapsed realtime timebase.
+    optional int64 expiration_time_elapsed = 1;
+
+    optional int64 window_size_ms = 2;
+    optional int32 count_limit = 3;
+
+    // The total number of events that occurred in the window.
+    optional int32 count_in_window = 4;
+
+    // The time after which the app will be under the bucket quota. This is only valid if
+    // count_in_window >= count_limit.
+    optional int64 in_quota_time_elapsed = 5;
+  }
+
+  message UptcStats {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional UptcProto uptc = 1;
+
+    // True if the UPTC has been given free quota.
+    optional bool is_quota_free = 2;
+
+    repeated Event events = 3;
+
+    repeated ExecutionStats execution_stats = 4;
+
+    optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 5;
+  }
+  repeated UptcStats uptc_stats = 3;
+
+  // Next tag: 4
+}
+
+// A com.android.util.quota.DurationQuotaTracker object.
+message DurationQuotaTrackerProto {
+  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  optional QuotaTrackerProto base_quota_data = 1;
+
+  message DurationLimit {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional CategoryProto category = 1;
+    optional int64 limit_ms = 2;
+    optional int64 window_size_ms = 3;
+  }
+  repeated DurationLimit duration_limit = 2;
+
+  message ExecutionStats {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // The time after which this record should be considered invalid (out of date), in the
+    // elapsed realtime timebase.
+    optional int64 expiration_time_elapsed = 1;
+
+    optional int32 window_size_ms = 2;
+    optional int64 duration_limit_ms = 3;
+
+    // The overall session duration in the window.
+    optional int64 session_duration_in_window_ms = 4;
+    // The number of individual long-running events in the window.
+    optional int32 event_count_in_window = 5;
+
+    // The time after which the app will be under the bucket quota. This is only valid if
+    // session_duration_in_window_ms >= duration_limit_ms.
+    optional int64 in_quota_time_elapsed = 6;
+  }
+
+  message Timer {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    // True if the Timer is actively tracking long-running events.
+    optional bool is_active = 1;
+    // The time this timer last became active. Only valid if is_active is true.
+    optional int64 start_time_elapsed = 2;
+    // How many long-running events are currently running. Valid only if is_active is true.
+    optional int32 event_count = 3;
+  }
+
+  message TimingSession {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int64 start_time_elapsed = 1;
+    optional int64 end_time_elapsed = 2;
+    // How many events started during this session. This only count long-running events, not
+    // instantaneous events.
+    optional int32 event_count = 3;
+  }
+
+  message UptcStats {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional UptcProto uptc = 1;
+
+    // True if the UPTC has been given free quota.
+    optional bool is_quota_free = 2;
+
+    optional Timer timer = 3;
+
+    repeated TimingSession saved_sessions = 4;
+
+    repeated ExecutionStats execution_stats = 5;
+
+    optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 6;
+  }
+  repeated UptcStats uptc_stats = 3;
+
+  message ReachedQuotaAlarmListener {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int64 trigger_time_elapsed = 1;
+
+    message UptcTimes {
+      option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+      optional UptcProto uptc = 1;
+      optional int64 out_of_quota_time_elapsed = 2;
+    }
+    repeated UptcTimes uptc_times = 2;
+  }
+  optional ReachedQuotaAlarmListener reached_quota_alarm_listener = 4;
+
+  // Next tag: 5
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c925744..ba2f64d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1850,6 +1850,13 @@
         android:description="@string/permdesc_vibrate"
         android:protectionLevel="normal|instant" />
 
+    <!-- Allows access to the vibrator always-on settings.
+         <p>Protection level: signature
+         @hide
+    -->
+    <permission android:name="android.permission.VIBRATE_ALWAYS_ON"
+        android:protectionLevel="signature" />
+
     <!-- Allows using PowerManager WakeLocks to keep processor from sleeping or screen
          from dimming.
          <p>Protection level: normal
@@ -4410,6 +4417,12 @@
     <permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows preempting sound trigger recognitions for the sake of capturing audio on
+         implementations which do not support running both concurrently.
+         @hide -->
+    <permission android:name="android.permission.PREEMPT_SOUND_TRIGGER"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Must be required by system/priv apps implementing sound trigger detection services
          @hide
          @SystemApi -->
diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml
index 9c725b9..4d7846d 100644
--- a/core/res/res/layout/chooser_grid_preview_text.xml
+++ b/core/res/res/layout/chooser_grid_preview_text.xml
@@ -45,7 +45,8 @@
         android:ellipsize="end"
         android:fontFamily="@android:string/config_headlineFontFamily"
         android:textColor="?android:attr/textColorPrimary"
-        android:maxLines="2"/>
+        android:maxLines="2"
+        android:focusable="true"/>
 
     <LinearLayout
         android:id="@+id/copy_button"
diff --git a/core/res/res/values-mcc510-mnc08/config.xml b/core/res/res/values-mcc510-mnc08/config.xml
index 7b27554..58fbb9e 100644
--- a/core/res/res/values-mcc510-mnc08/config.xml
+++ b/core/res/res/values-mcc510-mnc08/config.xml
@@ -23,4 +23,6 @@
          and "333" is used for other purpose -->
     <string-array translatable="false" name="config_callBarringMMI">
     </string-array>
+    <string-array translatable="false" name="config_callBarringMMI_for_ims">
+    </string-array>
 </resources>
diff --git a/core/res/res/values-mcc510-mnc89/config.xml b/core/res/res/values-mcc510-mnc89/config.xml
index 82efecf..c262247 100644
--- a/core/res/res/values-mcc510-mnc89/config.xml
+++ b/core/res/res/values-mcc510-mnc89/config.xml
@@ -23,4 +23,6 @@
          and "333" is used for other purpose -->
     <string-array translatable="false" name="config_callBarringMMI">
     </string-array>
+    <string-array translatable="false" name="config_callBarringMMI_for_ims">
+    </string-array>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 89c913b..03f8ebd 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2544,6 +2544,28 @@
     <string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
             >com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
 
+    <!-- Component name of the activity that shows the usb containment status. -->
+    <string name="config_usbContaminantActivity" translatable="false"
+            >com.android.systemui/com.android.systemui.usb.UsbContaminantActivity</string>
+
+    <!-- Component name of the activity that shows the request for access to a usb device. -->
+    <string name="config_usbPermissionActivity" translatable="false"
+            >com.android.systemui/com.android.systemui.usb.UsbPermissionActivity</string>
+
+    <!-- Component name of the activity that shows more information about a usb accessory. -->
+    <string name="config_usbAccessoryUriActivity" translatable="false"
+            >com.android.systemui/com.android.systemui.usb.UsbAccessoryUriActivity</string>
+
+    <!-- Component name of the activity that confirms the activity to start when a usb device is
+         plugged in. -->
+    <string name="config_usbConfirmActivity" translatable="false"
+            >com.android.systemui/com.android.systemui.usb.UsbConfirmActivity</string>
+
+    <!-- Component name of the activity to select the activity to start when a usb device is plugged
+         in. -->
+    <string name="config_usbResolverActivity" translatable="false"
+            >com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string>
+
     <!-- Name of the dialog that is used to request the user's consent to VPN connection -->
     <string name="config_customVpnConfirmDialogComponent" translatable="false"
             >com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog</string>
@@ -2630,6 +2652,18 @@
         <item>353</item>
     </string-array>
 
+    <!-- Ims supported call barring MMI code -->
+    <string-array translatable="false" name="config_callBarringMMI_for_ims">
+        <item>33</item>
+        <item>331</item>
+        <item>332</item>
+        <item>35</item>
+        <item>351</item>
+        <item>330</item>
+        <item>333</item>
+        <item>353</item>
+    </string-array>
+
     <!-- Override the default detection behavior for the framework method
          android.view.ViewConfiguration#hasPermanentMenuKey().
          Valid settings are:
@@ -2662,11 +2696,45 @@
          property. If this is false, then the following recents config flags are ignored. -->
     <bool name="config_hasRecents">true</bool>
 
-    <!-- Component name for the activity that will be presenting the Recents UI, which will receive special permissions for API related
-          to fetching and presenting recent tasks. The default configuration uses Launcehr3QuickStep as default launcher and points to
-          the corresponding recents component. When using a different default launcher, change this appropriately or use the default
-          systemui implementation: com.android.systemui/.recents.RecentsActivity -->
-    <string name="config_recentsComponentName" translatable="false">com.android.launcher3/com.android.quickstep.RecentsActivity</string>
+    <!-- Component name for the activity that will be presenting the Recents UI, which will receive
+         special permissions for API related to fetching and presenting recent tasks. The default
+         configuration uses Launcehr3QuickStep as default launcher and points to the corresponding
+         recents component. When using a different default launcher, change this appropriately or
+         use the default systemui implementation: com.android.systemui/.recents.RecentsActivity -->
+    <string name="config_recentsComponentName" translatable="false"
+            >com.android.launcher3/com.android.quickstep.RecentsActivity</string>
+
+    <!-- SystemUi service component -->
+    <string name="config_systemUIServiceComponent" translatable="false"
+            >com.android.systemui/com.android.systemui.SystemUIService</string>
+
+    <!-- Keyguard component -->
+    <string name="config_keyguardComponent" translatable="false"
+            >com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
+
+    <!-- Screen record dialog component -->
+    <string name="config_screenRecorderComponent" translatable="false"
+            >com.android.systemui/com.android.systemui.screenrecord.ScreenRecordDialog</string>
+
+    <!-- The component name of a special dock app that merely launches a dream.
+         We don't want to launch this app when docked because it causes an unnecessary
+         activity transition.  We just want to start the dream. -->
+    <string name="config_somnambulatorComponent" translatable="false"
+            >com.android.systemui/com.android.systemui.Somnambulator</string>
+
+    <!-- The component name of a special dock app that merely launches a dream.
+         We don't want to launch this app when docked because it causes an unnecessary
+         activity transition.  We just want to start the dream.. -->
+    <string name="config_screenshotServiceComponent" translatable="false"
+            >com.android.systemui/com.android.systemui.screenshot.TakeScreenshotService</string>
+
+    <!-- The component notified when there is an error while taking a screenshot. -->
+    <string name="config_screenshotErrorReceiverComponent" translatable="false"
+            >com.android.systemui/com.android.systemui.screenshot.ScreenshotServiceErrorReceiver</string>
+
+    <!-- The component for the activity shown to grant permissions for a slice. -->
+    <string name="config_slicePermissionComponent" translatable="false"
+            >com.android.systemui/com.android.systemui.SlicePermissionActivity</string>
 
     <!-- The minimum number of visible recent tasks to be presented to the user through the
          SystemUI. Can be -1 if there is no minimum limit. -->
@@ -2839,10 +2907,6 @@
     <!-- Whether to use voip audio mode for ims call -->
     <bool name="config_use_voip_mode_for_ims">false</bool>
 
-    <!-- ImsService package name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_ims_package"/>
-
     <!-- String array containing numbers that shouldn't be logged. Country-specific. -->
     <string-array name="unloggable_phone_numbers" />
 
@@ -3020,9 +3084,6 @@
     <!-- Specifies the maximum burn-in offset vertically. -->
     <integer name="config_burnInProtectionMaxVerticalOffset">0</integer>
 
-    <!-- Keyguard component -->
-    <string name="config_keyguardComponent" translatable="false">com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
-
     <!-- Limit for the number of face templates per user -->
     <integer name="config_faceMaxTemplatesPerUser">1</integer>
 
@@ -3061,7 +3122,9 @@
 
          The path is assumed to be specified in display coordinates with pixel units and in
          the display's native orientation, with the origin of the coordinate system at the
-         center top of the display.
+         center top of the display. Optionally, you can append either `@left` or `@right` to the
+         end of the path string, in order to change the path origin to either the top left,
+         or top right of the display.
 
          To facilitate writing device-independent emulation overlays, the marker `@dp` can be
          appended after the path string to interpret coordinates in dp instead of px units.
@@ -4095,6 +4158,14 @@
     <!-- Which binder services to include in incident reports containing restricted images. -->
     <string-array name="config_restrictedImagesServices" translatable="false"/>
 
+    <!-- List of biometric sensors on the device, in decreasing strength. Consumed by AuthService
+         when registering authenticators with BiometricService. Format must be ID:Modality:Strength,
+         where: IDs are unique per device, Modality as defined in BiometricAuthenticator.java,
+         and Strength as defined in Authenticators.java -->
+    <string-array name="config_biometric_sensors" translatable="false" >
+        <item>0:2:15</item> <!-- ID0:Fingerprint:Strong -->
+    </string-array>
+
     <!-- Messages that should not be shown to the user during face auth enrollment. This should be
          used to hide messages that may be too chatty or messages that the user can't do much about.
          Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index de1b5ba..ab10738 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4946,8 +4946,12 @@
     <!-- Resolver target actions strings -->
     <!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]-->
     <string name="pin_target">Pin</string>
+    <!-- Pin this app to the top of the Sharesheet app list. [CHAR LIMIT=60]-->
+    <string name="pin_specific_target">Pin <xliff:g id="label" example="Tweet">%1$s</xliff:g></string>
     <!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]-->
-    <string name="unpin_target">Unpin</string>
+    <string name="unpin_target">Unpin </string>
+    <!-- Un-pin this app in the Sharesheet, so that it is sorted normally. [CHAR LIMIT=60]-->
+    <string name="unpin_specific_target">Unpin <xliff:g id="label" example="Tweet">%1$s</xliff:g></string>
     <!-- View application info for a target. -->
     <string name="app_info">App info</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6956c39..a8d30c1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -295,7 +295,6 @@
   <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
-  <java-symbol type="string" name="config_ims_package" />
   <java-symbol type="string" name="config_wwan_network_service_package" />
   <java-symbol type="string" name="config_wlan_network_service_package" />
   <java-symbol type="string" name="config_wwan_network_service_class" />
@@ -360,6 +359,17 @@
   <java-symbol type="bool" name="config_use16BitTaskSnapshotPixelFormat" />
   <java-symbol type="bool" name="config_hasRecents" />
   <java-symbol type="string" name="config_recentsComponentName" />
+  <java-symbol type="string" name="config_systemUIServiceComponent" />
+  <java-symbol type="string" name="config_screenRecorderComponent" />
+  <java-symbol type="string" name="config_somnambulatorComponent" />
+  <java-symbol type="string" name="config_screenshotServiceComponent" />
+  <java-symbol type="string" name="config_screenshotErrorReceiverComponent" />
+  <java-symbol type="string" name="config_slicePermissionComponent" />
+  <java-symbol type="string" name="config_usbContaminantActivity" />
+  <java-symbol type="string" name="config_usbPermissionActivity" />
+  <java-symbol type="string" name="config_usbAccessoryUriActivity" />
+  <java-symbol type="string" name="config_usbConfirmActivity" />
+  <java-symbol type="string" name="config_usbResolverActivity" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
@@ -1231,6 +1241,7 @@
   <java-symbol type="array" name="config_cdma_dun_supported_types" />
   <java-symbol type="array" name="config_disabledUntilUsedPreinstalledImes" />
   <java-symbol type="array" name="config_callBarringMMI" />
+  <java-symbol type="array" name="config_callBarringMMI_for_ims" />
   <java-symbol type="array" name="config_globalActionsList" />
   <java-symbol type="array" name="config_telephonyEuiccDeviceCapabilities" />
   <java-symbol type="array" name="config_telephonyHardware" />
@@ -2471,6 +2482,8 @@
   <java-symbol type="string" name="face_authenticated_no_confirmation_required" />
   <java-symbol type="string" name="face_authenticated_confirmation_required" />
 
+  <java-symbol type="array" name="config_biometric_sensors" />
+
   <java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
   <java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
   <java-symbol type="array" name="config_face_acquire_keyguard_ignorelist" />
@@ -2944,6 +2957,8 @@
   <!-- Resolver target actions -->
   <java-symbol type="array" name="resolver_target_actions_pin" />
   <java-symbol type="array" name="resolver_target_actions_unpin" />
+  <java-symbol type="string" name="pin_specific_target" />
+  <java-symbol type="string" name="unpin_specific_target" />
 
   <java-symbol type="array" name="non_removable_euicc_slots" />
 
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 1a48260..68d95cd 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -19,11 +19,9 @@
 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.navigationBars;
 import static android.view.WindowInsets.Type.systemBars;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.eq;
@@ -118,7 +116,8 @@
         consumers.put(ITYPE_NAVIGATION_BAR, navConsumer);
         mController = new InsetsAnimationControlImpl(consumers,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
-                () -> mMockTransactionApplier, mMockController);
+                () -> mMockTransactionApplier, mMockController, 10 /* durationMs */,
+                false /* fade */);
     }
 
     @Test
@@ -131,9 +130,11 @@
 
     @Test
     public void testChangeInsets() {
-        mController.changeInsets(Insets.of(0, 30, 40, 0));
+        mController.setInsetsAndAlpha(Insets.of(0, 30, 40, 0), 1f /* alpha */,
+                0f /* fraction */);
         mController.applyChangeInsets(new InsetsState());
         assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
+        assertEquals(1f, mController.getCurrentAlpha(), 1f - mController.getCurrentAlpha());
 
         ArgumentCaptor<SurfaceParams> captor = ArgumentCaptor.forClass(SurfaceParams.class);
         verify(mMockTransactionApplier).scheduleApply(captor.capture());
@@ -148,26 +149,43 @@
     }
 
     @Test
+    public void testChangeAlphaNoInsets() {
+        Insets initialInsets = mController.getCurrentInsets();
+        mController.setInsetsAndAlpha(null, 0.5f, 0f /* fraction*/);
+        mController.applyChangeInsets(new InsetsState());
+        assertEquals(0.5f, mController.getCurrentAlpha(), 0.5f - mController.getCurrentAlpha());
+        assertEquals(initialInsets, mController.getCurrentInsets());
+    }
+
+    @Test
+    public void testChangeInsetsAndAlpha() {
+        mController.setInsetsAndAlpha(Insets.of(0, 30, 40, 0), 0.5f, 1f);
+        mController.applyChangeInsets(new InsetsState());
+        assertEquals(0.5f, mController.getCurrentAlpha(), 0.5f - mController.getCurrentAlpha());
+        assertEquals(Insets.of(0, 30, 40, 0), mController.getCurrentInsets());
+    }
+
+    @Test
     public void testFinishing() {
         when(mMockController.getState()).thenReturn(mInsetsState);
-        mController.finish(navigationBars());
+        mController.finish(true /* shown */);
         mController.applyChangeInsets(mInsetsState);
-        assertFalse(mInsetsState.getSource(ITYPE_STATUS_BAR).isVisible());
+        assertTrue(mInsetsState.getSource(ITYPE_STATUS_BAR).isVisible());
         assertTrue(mInsetsState.getSource(ITYPE_NAVIGATION_BAR).isVisible());
-        assertEquals(Insets.of(0, 0, 100, 0), mController.getCurrentInsets());
-        verify(mMockController).notifyFinished(eq(mController), eq(navigationBars()));
+        assertEquals(Insets.of(0, 100, 100, 0), mController.getCurrentInsets());
+        verify(mMockController).notifyFinished(eq(mController), eq(true /* shown */));
     }
 
     @Test
     public void testCancelled() {
         mController.onCancelled();
         try {
-            mController.changeInsets(Insets.NONE);
+            mController.setInsetsAndAlpha(Insets.NONE, 1f /*alpha */, 0f /* fraction */);
             fail("Expected exception to be thrown");
         } catch (IllegalStateException ignored) {
         }
         verify(mMockListener).onCancelled();
-        mController.finish(navigationBars());
+        mController.finish(true /* shown */);
     }
 
     private void assertPosition(Matrix m, Rect original, Rect transformed) {
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index e4d8279..a89fc1e 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -123,7 +123,7 @@
 
         WindowInsetsAnimationControlListener mockListener =
                 mock(WindowInsetsAnimationControlListener.class);
-        mController.controlWindowInsetsAnimation(statusBars(), mockListener);
+        mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener);
         verify(mockListener).onReady(any(), anyInt());
         mController.onControlsChanged(new InsetsSourceControl[0]);
         verify(mockListener).onCancelled();
@@ -135,7 +135,7 @@
         mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200));
         WindowInsetsAnimationControlListener controlListener =
                 mock(WindowInsetsAnimationControlListener.class);
-        mController.controlWindowInsetsAnimation(0, controlListener);
+        mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, controlListener);
         verify(controlListener).onCancelled();
         verify(controlListener, never()).onReady(any(), anyInt());
     }
@@ -331,12 +331,13 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener mockListener =
                     mock(WindowInsetsAnimationControlListener.class);
-            mController.controlWindowInsetsAnimation(statusBars(), mockListener);
+            mController.controlWindowInsetsAnimation(statusBars(), 0 /* durationMs */,
+                    mockListener);
 
             ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
                     ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
             verify(mockListener).onReady(controllerCaptor.capture(), anyInt());
-            controllerCaptor.getValue().finish(0 /* shownTypes */);
+            controllerCaptor.getValue().finish(false /* shown */);
         });
         waitUntilNextFrame();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
index 9002c2c..ffc925f 100644
--- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -111,7 +111,7 @@
     }
 
     @Test
-    public void testWriteToParcel_Exceptionally() throws Exception {
+    public void testWriteToParcel_Exception() throws Exception {
         Parcel parcel = Parcel.obtain();
         AndroidFuture<Integer> future1 = new AndroidFuture<>();
         future1.completeExceptionally(new UnsupportedOperationException());
@@ -123,4 +123,30 @@
                 expectThrows(ExecutionException.class, future2::get);
         assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
     }
+
+    @Test
+    public void testWriteToParcel_Incomplete() throws Exception {
+        Parcel parcel = Parcel.obtain();
+        AndroidFuture<Integer> future1 = new AndroidFuture<>();
+        future1.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
+        future2.complete(5);
+        assertThat(future1.get()).isEqualTo(5);
+    }
+
+    @Test
+    public void testWriteToParcel_Incomplete_Exception() throws Exception {
+        Parcel parcel = Parcel.obtain();
+        AndroidFuture<Integer> future1 = new AndroidFuture<>();
+        future1.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);
+        AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
+        future2.completeExceptionally(new UnsupportedOperationException());
+        ExecutionException executionException =
+                expectThrows(ExecutionException.class, future1::get);
+        assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
+    }
 }
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index e16d1ca..1472b90 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -24,8 +24,13 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Looper;
 
@@ -54,9 +59,15 @@
         // This raises a `SecurityException` if the device is locked. Calling either `Context`
         // method results in a broadcast of `android.intent.action. USER_PRESENT`. Only the system
         // process is allowed to broadcast that `Intent`.
+        Resources res = mock(Resources.class);
         mContext = Mockito.spy(Context.class);
-        Mockito.doNothing().when(mContext).sendBroadcastAsUser(any(), any());
-        Mockito.doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
+        doNothing().when(mContext).sendBroadcastAsUser(any(), any());
+        doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
+        doReturn(res).when(mContext).getResources();
+        doReturn("com.android.systemui/.Service").when(res).getString(
+                eq(com.android.internal.R.string.config_screenshotServiceComponent));
+        doReturn("com.android.systemui/.ErrorReceiver").when(res).getString(
+                eq(com.android.internal.R.string.config_screenshotErrorReceiverComponent));
 
         mHandler = new Handler(Looper.getMainLooper());
         mScreenshotHelper = new ScreenshotHelper(mContext);
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 80098c5..0574775 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -157,6 +157,7 @@
     <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
+    <assign-permission name="android.permission.PREEMPT_SOUND_TRIGGER" uid="audioserver" />
 
     <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 322cbd7..624fc50 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -145,7 +145,7 @@
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
         <permission name="android.permission.CHANGE_CONFIGURATION"/>
         <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
-        <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+        <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
         <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
         <permission name="android.permission.DUMP"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
@@ -240,6 +240,13 @@
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.tethering">
+        <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.UPDATE_APP_OPS_STATS"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.server.telecom">
         <permission name="android.permission.BIND_CONNECTION_SERVICE"/>
         <permission name="android.permission.BIND_INCALL_SERVICE"/>
@@ -343,6 +350,8 @@
         <permission name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
         <!-- Permission required for Telecom car mode CTS tests. -->
         <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+        <!-- Permission required for Tethering CTS tests. -->
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
new file mode 100644
index 0000000..d8af726
--- /dev/null
+++ b/framework-jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule android.hidl.** android.internal.hidl.@1
+rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 2648008..5ad93f4 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.graphics.fonts.FontVariationAxis;
 import android.text.TextUtils;
@@ -57,6 +58,7 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
+    @UnsupportedAppUsage(trackingBug = 123768928)
     public long mNativePtr;
 
     // Points native font family builder. Must be zero after freezing this family.
@@ -65,6 +67,7 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
+    @UnsupportedAppUsage(trackingBug = 123768928)
     public FontFamily() {
         mBuilderPtr = nInitBuilder(null, 0);
         mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
@@ -73,6 +76,7 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
+    @UnsupportedAppUsage(trackingBug = 123768928)
     public FontFamily(@Nullable String[] langs, int variant) {
         final String langsString;
         if (langs == null || langs.length == 0) {
@@ -94,6 +98,7 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
+    @UnsupportedAppUsage(trackingBug = 123768928)
     public boolean freeze() {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("This FontFamily is already frozen");
@@ -110,6 +115,7 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
+    @UnsupportedAppUsage(trackingBug = 123768928)
     public void abortCreation() {
         if (mBuilderPtr == 0) {
             throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -121,6 +127,7 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
+    @UnsupportedAppUsage(trackingBug = 123768928)
     public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
             int italic) {
         if (mBuilderPtr == 0) {
@@ -144,6 +151,7 @@
     /**
      * This cannot be deleted because it's in use by AndroidX.
      */
+    @UnsupportedAppUsage(trackingBug = 123768928)
     public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
             int weight, int italic) {
         if (mBuilderPtr == 0) {
@@ -171,6 +179,7 @@
      *
      * This cannot be deleted because it's in use by AndroidX.
      */
+    @UnsupportedAppUsage(trackingBug = 123768928)
     public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
             boolean isAsset, int ttcIndex, int weight, int isItalic,
             FontVariationAxis[] axes) {
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 8ebac66..aecef8e 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -270,7 +270,7 @@
         public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
             AssetFileDescriptor assetFd = null;
             try {
-                if (mUri.getScheme() == ContentResolver.SCHEME_CONTENT) {
+                if (mUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
                     assetFd = mResolver.openTypedAssetFileDescriptor(mUri,
                             "image/*", null);
                 } else {
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 45b2de5..c6586ec 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -784,9 +784,7 @@
             mFillPaint.setDither(st.mDither);
             mFillPaint.setColorFilter(colorFilter);
             if (colorFilter != null && st.mSolidColors == null) {
-                // If we don't have a solid color and we don't have a gradient,
-                // the app is stroking the shape, set the color to transparent
-                mFillPaint.setColor(st.mGradientColors != null ? mAlpha << 24 : 0);
+                mFillPaint.setColor(mAlpha << 24);
             }
             if (haveStroke) {
                 mStrokePaint.setAlpha(currStrokeAlpha);
diff --git a/jarjar_rules_hidl.txt b/jarjar_rules_hidl.txt
deleted file mode 100644
index 4b2331d..0000000
--- a/jarjar_rules_hidl.txt
+++ /dev/null
@@ -1 +0,0 @@
-rule android.hidl.** android.internal.hidl.@1
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 572fa8c..048dee6 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -65,6 +65,9 @@
     /** Key prefix for VPN. */
     public static final String VPN = "VPN_";
 
+    /** Key prefix for platform VPNs. */
+    public static final String PLATFORM_VPN = "PLATFORM_VPN_";
+
     /** Key prefix for WIFI. */
     public static final String WIFI = "WIFI_";
 
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index c276a23..c462eb7 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -10,1439 +10,1466 @@
     /* 6  */ {'B', 'a', 's', 's'},
     /* 7  */ {'B', 'e', 'n', 'g'},
     /* 8  */ {'B', 'r', 'a', 'h'},
-    /* 9  */ {'C', 'a', 'n', 's'},
-    /* 10 */ {'C', 'a', 'r', 'i'},
-    /* 11 */ {'C', 'h', 'a', 'm'},
-    /* 12 */ {'C', 'h', 'e', 'r'},
-    /* 13 */ {'C', 'o', 'p', 't'},
-    /* 14 */ {'C', 'p', 'r', 't'},
-    /* 15 */ {'C', 'y', 'r', 'l'},
-    /* 16 */ {'D', 'e', 'v', 'a'},
-    /* 17 */ {'E', 'g', 'y', 'p'},
-    /* 18 */ {'E', 't', 'h', 'i'},
-    /* 19 */ {'G', 'e', 'o', 'r'},
-    /* 20 */ {'G', 'o', 't', 'h'},
-    /* 21 */ {'G', 'r', 'e', 'k'},
-    /* 22 */ {'G', 'u', 'j', 'r'},
-    /* 23 */ {'G', 'u', 'r', 'u'},
-    /* 24 */ {'H', 'a', 'n', 's'},
-    /* 25 */ {'H', 'a', 'n', 't'},
-    /* 26 */ {'H', 'a', 't', 'r'},
-    /* 27 */ {'H', 'e', 'b', 'r'},
-    /* 28 */ {'H', 'l', 'u', 'w'},
-    /* 29 */ {'H', 'm', 'n', 'g'},
-    /* 30 */ {'I', 't', 'a', 'l'},
-    /* 31 */ {'J', 'p', 'a', 'n'},
-    /* 32 */ {'K', 'a', 'l', 'i'},
-    /* 33 */ {'K', 'a', 'n', 'a'},
-    /* 34 */ {'K', 'h', 'a', 'r'},
-    /* 35 */ {'K', 'h', 'm', 'r'},
-    /* 36 */ {'K', 'n', 'd', 'a'},
-    /* 37 */ {'K', 'o', 'r', 'e'},
-    /* 38 */ {'L', 'a', 'n', 'a'},
-    /* 39 */ {'L', 'a', 'o', 'o'},
-    /* 40 */ {'L', 'a', 't', 'n'},
-    /* 41 */ {'L', 'e', 'p', 'c'},
-    /* 42 */ {'L', 'i', 'n', 'a'},
-    /* 43 */ {'L', 'i', 's', 'u'},
-    /* 44 */ {'L', 'y', 'c', 'i'},
-    /* 45 */ {'L', 'y', 'd', 'i'},
-    /* 46 */ {'M', 'a', 'n', 'd'},
-    /* 47 */ {'M', 'a', 'n', 'i'},
-    /* 48 */ {'M', 'e', 'r', 'c'},
-    /* 49 */ {'M', 'l', 'y', 'm'},
-    /* 50 */ {'M', 'o', 'n', 'g'},
-    /* 51 */ {'M', 'r', 'o', 'o'},
-    /* 52 */ {'M', 'y', 'm', 'r'},
-    /* 53 */ {'N', 'a', 'r', 'b'},
-    /* 54 */ {'N', 'k', 'o', 'o'},
-    /* 55 */ {'O', 'g', 'a', 'm'},
-    /* 56 */ {'O', 'r', 'k', 'h'},
-    /* 57 */ {'O', 'r', 'y', 'a'},
-    /* 58 */ {'O', 's', 'g', 'e'},
-    /* 59 */ {'P', 'a', 'u', 'c'},
-    /* 60 */ {'P', 'h', 'l', 'i'},
-    /* 61 */ {'P', 'h', 'n', 'x'},
-    /* 62 */ {'P', 'l', 'r', 'd'},
-    /* 63 */ {'P', 'r', 't', 'i'},
-    /* 64 */ {'R', 'u', 'n', 'r'},
-    /* 65 */ {'S', 'a', 'm', 'r'},
-    /* 66 */ {'S', 'a', 'r', 'b'},
-    /* 67 */ {'S', 'a', 'u', 'r'},
-    /* 68 */ {'S', 'g', 'n', 'w'},
-    /* 69 */ {'S', 'i', 'n', 'h'},
-    /* 70 */ {'S', 'o', 'r', 'a'},
-    /* 71 */ {'S', 'y', 'r', 'c'},
-    /* 72 */ {'T', 'a', 'l', 'e'},
-    /* 73 */ {'T', 'a', 'l', 'u'},
-    /* 74 */ {'T', 'a', 'm', 'l'},
-    /* 75 */ {'T', 'a', 'n', 'g'},
-    /* 76 */ {'T', 'a', 'v', 't'},
-    /* 77 */ {'T', 'e', 'l', 'u'},
-    /* 78 */ {'T', 'f', 'n', 'g'},
-    /* 79 */ {'T', 'h', 'a', 'a'},
-    /* 80 */ {'T', 'h', 'a', 'i'},
-    /* 81 */ {'T', 'i', 'b', 't'},
-    /* 82 */ {'U', 'g', 'a', 'r'},
-    /* 83 */ {'V', 'a', 'i', 'i'},
-    /* 84 */ {'X', 'p', 'e', 'o'},
-    /* 85 */ {'X', 's', 'u', 'x'},
-    /* 86 */ {'Y', 'i', 'i', 'i'},
-    /* 87 */ {'~', '~', '~', 'A'},
-    /* 88 */ {'~', '~', '~', 'B'},
+    /* 9  */ {'C', 'a', 'k', 'm'},
+    /* 10 */ {'C', 'a', 'n', 's'},
+    /* 11 */ {'C', 'a', 'r', 'i'},
+    /* 12 */ {'C', 'h', 'a', 'm'},
+    /* 13 */ {'C', 'h', 'e', 'r'},
+    /* 14 */ {'C', 'o', 'p', 't'},
+    /* 15 */ {'C', 'p', 'r', 't'},
+    /* 16 */ {'C', 'y', 'r', 'l'},
+    /* 17 */ {'D', 'e', 'v', 'a'},
+    /* 18 */ {'E', 'g', 'y', 'p'},
+    /* 19 */ {'E', 't', 'h', 'i'},
+    /* 20 */ {'G', 'e', 'o', 'r'},
+    /* 21 */ {'G', 'o', 'n', 'g'},
+    /* 22 */ {'G', 'o', 'n', 'm'},
+    /* 23 */ {'G', 'o', 't', 'h'},
+    /* 24 */ {'G', 'r', 'e', 'k'},
+    /* 25 */ {'G', 'u', 'j', 'r'},
+    /* 26 */ {'G', 'u', 'r', 'u'},
+    /* 27 */ {'H', 'a', 'n', 's'},
+    /* 28 */ {'H', 'a', 'n', 't'},
+    /* 29 */ {'H', 'a', 't', 'r'},
+    /* 30 */ {'H', 'e', 'b', 'r'},
+    /* 31 */ {'H', 'l', 'u', 'w'},
+    /* 32 */ {'H', 'm', 'n', 'g'},
+    /* 33 */ {'H', 'm', 'n', 'p'},
+    /* 34 */ {'I', 't', 'a', 'l'},
+    /* 35 */ {'J', 'p', 'a', 'n'},
+    /* 36 */ {'K', 'a', 'l', 'i'},
+    /* 37 */ {'K', 'a', 'n', 'a'},
+    /* 38 */ {'K', 'h', 'a', 'r'},
+    /* 39 */ {'K', 'h', 'm', 'r'},
+    /* 40 */ {'K', 'n', 'd', 'a'},
+    /* 41 */ {'K', 'o', 'r', 'e'},
+    /* 42 */ {'L', 'a', 'n', 'a'},
+    /* 43 */ {'L', 'a', 'o', 'o'},
+    /* 44 */ {'L', 'a', 't', 'n'},
+    /* 45 */ {'L', 'e', 'p', 'c'},
+    /* 46 */ {'L', 'i', 'n', 'a'},
+    /* 47 */ {'L', 'i', 's', 'u'},
+    /* 48 */ {'L', 'y', 'c', 'i'},
+    /* 49 */ {'L', 'y', 'd', 'i'},
+    /* 50 */ {'M', 'a', 'n', 'd'},
+    /* 51 */ {'M', 'a', 'n', 'i'},
+    /* 52 */ {'M', 'e', 'r', 'c'},
+    /* 53 */ {'M', 'l', 'y', 'm'},
+    /* 54 */ {'M', 'o', 'n', 'g'},
+    /* 55 */ {'M', 'r', 'o', 'o'},
+    /* 56 */ {'M', 'y', 'm', 'r'},
+    /* 57 */ {'N', 'a', 'r', 'b'},
+    /* 58 */ {'N', 'k', 'o', 'o'},
+    /* 59 */ {'N', 's', 'h', 'u'},
+    /* 60 */ {'O', 'g', 'a', 'm'},
+    /* 61 */ {'O', 'r', 'k', 'h'},
+    /* 62 */ {'O', 'r', 'y', 'a'},
+    /* 63 */ {'O', 's', 'g', 'e'},
+    /* 64 */ {'P', 'a', 'u', 'c'},
+    /* 65 */ {'P', 'h', 'l', 'i'},
+    /* 66 */ {'P', 'h', 'n', 'x'},
+    /* 67 */ {'P', 'l', 'r', 'd'},
+    /* 68 */ {'P', 'r', 't', 'i'},
+    /* 69 */ {'R', 'u', 'n', 'r'},
+    /* 70 */ {'S', 'a', 'm', 'r'},
+    /* 71 */ {'S', 'a', 'r', 'b'},
+    /* 72 */ {'S', 'a', 'u', 'r'},
+    /* 73 */ {'S', 'g', 'n', 'w'},
+    /* 74 */ {'S', 'i', 'n', 'h'},
+    /* 75 */ {'S', 'o', 'g', 'd'},
+    /* 76 */ {'S', 'o', 'r', 'a'},
+    /* 77 */ {'S', 'o', 'y', 'o'},
+    /* 78 */ {'S', 'y', 'r', 'c'},
+    /* 79 */ {'T', 'a', 'l', 'e'},
+    /* 80 */ {'T', 'a', 'l', 'u'},
+    /* 81 */ {'T', 'a', 'm', 'l'},
+    /* 82 */ {'T', 'a', 'n', 'g'},
+    /* 83 */ {'T', 'a', 'v', 't'},
+    /* 84 */ {'T', 'e', 'l', 'u'},
+    /* 85 */ {'T', 'f', 'n', 'g'},
+    /* 86 */ {'T', 'h', 'a', 'a'},
+    /* 87 */ {'T', 'h', 'a', 'i'},
+    /* 88 */ {'T', 'i', 'b', 't'},
+    /* 89 */ {'U', 'g', 'a', 'r'},
+    /* 90 */ {'V', 'a', 'i', 'i'},
+    /* 91 */ {'W', 'c', 'h', 'o'},
+    /* 92 */ {'X', 'p', 'e', 'o'},
+    /* 93 */ {'X', 's', 'u', 'x'},
+    /* 94 */ {'Y', 'i', 'i', 'i'},
+    /* 95 */ {'~', '~', '~', 'A'},
+    /* 96 */ {'~', '~', '~', 'B'},
 };
 
 
 const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
-    {0x61610000u, 40u}, // aa -> Latn
-    {0xA0000000u, 40u}, // aai -> Latn
-    {0xA8000000u, 40u}, // aak -> Latn
-    {0xD0000000u, 40u}, // aau -> Latn
-    {0x61620000u, 15u}, // ab -> Cyrl
-    {0xA0200000u, 40u}, // abi -> Latn
-    {0xC4200000u, 40u}, // abr -> Latn
-    {0xCC200000u, 40u}, // abt -> Latn
-    {0xE0200000u, 40u}, // aby -> Latn
-    {0x8C400000u, 40u}, // acd -> Latn
-    {0x90400000u, 40u}, // ace -> Latn
-    {0x9C400000u, 40u}, // ach -> Latn
-    {0x80600000u, 40u}, // ada -> Latn
-    {0x90600000u, 40u}, // ade -> Latn
-    {0xA4600000u, 40u}, // adj -> Latn
-    {0xE0600000u, 15u}, // ady -> Cyrl
-    {0xE4600000u, 40u}, // adz -> Latn
+    {0x61610000u, 44u}, // aa -> Latn
+    {0xA0000000u, 44u}, // aai -> Latn
+    {0xA8000000u, 44u}, // aak -> Latn
+    {0xD0000000u, 44u}, // aau -> Latn
+    {0x61620000u, 16u}, // ab -> Cyrl
+    {0xA0200000u, 44u}, // abi -> Latn
+    {0xC0200000u, 16u}, // abq -> Cyrl
+    {0xC4200000u, 44u}, // abr -> Latn
+    {0xCC200000u, 44u}, // abt -> Latn
+    {0xE0200000u, 44u}, // aby -> Latn
+    {0x8C400000u, 44u}, // acd -> Latn
+    {0x90400000u, 44u}, // ace -> Latn
+    {0x9C400000u, 44u}, // ach -> Latn
+    {0x80600000u, 44u}, // ada -> Latn
+    {0x90600000u, 44u}, // ade -> Latn
+    {0xA4600000u, 44u}, // adj -> Latn
+    {0xE0600000u, 16u}, // ady -> Cyrl
+    {0xE4600000u, 44u}, // adz -> Latn
     {0x61650000u,  4u}, // ae -> Avst
     {0x84800000u,  1u}, // aeb -> Arab
-    {0xE0800000u, 40u}, // aey -> Latn
-    {0x61660000u, 40u}, // af -> Latn
-    {0x88C00000u, 40u}, // agc -> Latn
-    {0x8CC00000u, 40u}, // agd -> Latn
-    {0x98C00000u, 40u}, // agg -> Latn
-    {0xB0C00000u, 40u}, // agm -> Latn
-    {0xB8C00000u, 40u}, // ago -> Latn
-    {0xC0C00000u, 40u}, // agq -> Latn
-    {0x80E00000u, 40u}, // aha -> Latn
-    {0xACE00000u, 40u}, // ahl -> Latn
+    {0xE0800000u, 44u}, // aey -> Latn
+    {0x61660000u, 44u}, // af -> Latn
+    {0x88C00000u, 44u}, // agc -> Latn
+    {0x8CC00000u, 44u}, // agd -> Latn
+    {0x98C00000u, 44u}, // agg -> Latn
+    {0xB0C00000u, 44u}, // agm -> Latn
+    {0xB8C00000u, 44u}, // ago -> Latn
+    {0xC0C00000u, 44u}, // agq -> Latn
+    {0x80E00000u, 44u}, // aha -> Latn
+    {0xACE00000u, 44u}, // ahl -> Latn
     {0xB8E00000u,  0u}, // aho -> Ahom
-    {0x99200000u, 40u}, // ajg -> Latn
-    {0x616B0000u, 40u}, // ak -> Latn
-    {0xA9400000u, 85u}, // akk -> Xsux
-    {0x81600000u, 40u}, // ala -> Latn
-    {0xA1600000u, 40u}, // ali -> Latn
-    {0xB5600000u, 40u}, // aln -> Latn
-    {0xCD600000u, 15u}, // alt -> Cyrl
-    {0x616D0000u, 18u}, // am -> Ethi
-    {0xB1800000u, 40u}, // amm -> Latn
-    {0xB5800000u, 40u}, // amn -> Latn
-    {0xB9800000u, 40u}, // amo -> Latn
-    {0xBD800000u, 40u}, // amp -> Latn
-    {0x89A00000u, 40u}, // anc -> Latn
-    {0xA9A00000u, 40u}, // ank -> Latn
-    {0xB5A00000u, 40u}, // ann -> Latn
-    {0xE1A00000u, 40u}, // any -> Latn
-    {0xA5C00000u, 40u}, // aoj -> Latn
-    {0xB1C00000u, 40u}, // aom -> Latn
-    {0xE5C00000u, 40u}, // aoz -> Latn
+    {0x99200000u, 44u}, // ajg -> Latn
+    {0x616B0000u, 44u}, // ak -> Latn
+    {0xA9400000u, 93u}, // akk -> Xsux
+    {0x81600000u, 44u}, // ala -> Latn
+    {0xA1600000u, 44u}, // ali -> Latn
+    {0xB5600000u, 44u}, // aln -> Latn
+    {0xCD600000u, 16u}, // alt -> Cyrl
+    {0x616D0000u, 19u}, // am -> Ethi
+    {0xB1800000u, 44u}, // amm -> Latn
+    {0xB5800000u, 44u}, // amn -> Latn
+    {0xB9800000u, 44u}, // amo -> Latn
+    {0xBD800000u, 44u}, // amp -> Latn
+    {0x89A00000u, 44u}, // anc -> Latn
+    {0xA9A00000u, 44u}, // ank -> Latn
+    {0xB5A00000u, 44u}, // ann -> Latn
+    {0xE1A00000u, 44u}, // any -> Latn
+    {0xA5C00000u, 44u}, // aoj -> Latn
+    {0xB1C00000u, 44u}, // aom -> Latn
+    {0xE5C00000u, 44u}, // aoz -> Latn
     {0x89E00000u,  1u}, // apc -> Arab
     {0x8DE00000u,  1u}, // apd -> Arab
-    {0x91E00000u, 40u}, // ape -> Latn
-    {0xC5E00000u, 40u}, // apr -> Latn
-    {0xC9E00000u, 40u}, // aps -> Latn
-    {0xE5E00000u, 40u}, // apz -> Latn
+    {0x91E00000u, 44u}, // ape -> Latn
+    {0xC5E00000u, 44u}, // apr -> Latn
+    {0xC9E00000u, 44u}, // aps -> Latn
+    {0xE5E00000u, 44u}, // apz -> Latn
     {0x61720000u,  1u}, // ar -> Arab
-    {0x61725842u, 88u}, // ar-XB -> ~~~B
+    {0x61725842u, 96u}, // ar-XB -> ~~~B
     {0x8A200000u,  2u}, // arc -> Armi
-    {0x9E200000u, 40u}, // arh -> Latn
-    {0xB6200000u, 40u}, // arn -> Latn
-    {0xBA200000u, 40u}, // aro -> Latn
+    {0x9E200000u, 44u}, // arh -> Latn
+    {0xB6200000u, 44u}, // arn -> Latn
+    {0xBA200000u, 44u}, // aro -> Latn
     {0xC2200000u,  1u}, // arq -> Arab
     {0xE2200000u,  1u}, // ary -> Arab
     {0xE6200000u,  1u}, // arz -> Arab
     {0x61730000u,  7u}, // as -> Beng
-    {0x82400000u, 40u}, // asa -> Latn
-    {0x92400000u, 68u}, // ase -> Sgnw
-    {0x9A400000u, 40u}, // asg -> Latn
-    {0xBA400000u, 40u}, // aso -> Latn
-    {0xCE400000u, 40u}, // ast -> Latn
-    {0x82600000u, 40u}, // ata -> Latn
-    {0x9A600000u, 40u}, // atg -> Latn
-    {0xA6600000u, 40u}, // atj -> Latn
-    {0xE2800000u, 40u}, // auy -> Latn
-    {0x61760000u, 15u}, // av -> Cyrl
+    {0x82400000u, 44u}, // asa -> Latn
+    {0x92400000u, 73u}, // ase -> Sgnw
+    {0x9A400000u, 44u}, // asg -> Latn
+    {0xBA400000u, 44u}, // aso -> Latn
+    {0xCE400000u, 44u}, // ast -> Latn
+    {0x82600000u, 44u}, // ata -> Latn
+    {0x9A600000u, 44u}, // atg -> Latn
+    {0xA6600000u, 44u}, // atj -> Latn
+    {0xE2800000u, 44u}, // auy -> Latn
+    {0x61760000u, 16u}, // av -> Cyrl
     {0xAEA00000u,  1u}, // avl -> Arab
-    {0xB6A00000u, 40u}, // avn -> Latn
-    {0xCEA00000u, 40u}, // avt -> Latn
-    {0xD2A00000u, 40u}, // avu -> Latn
-    {0x82C00000u, 16u}, // awa -> Deva
-    {0x86C00000u, 40u}, // awb -> Latn
-    {0xBAC00000u, 40u}, // awo -> Latn
-    {0xDEC00000u, 40u}, // awx -> Latn
-    {0x61790000u, 40u}, // ay -> Latn
-    {0x87000000u, 40u}, // ayb -> Latn
-    {0x617A0000u, 40u}, // az -> Latn
+    {0xB6A00000u, 44u}, // avn -> Latn
+    {0xCEA00000u, 44u}, // avt -> Latn
+    {0xD2A00000u, 44u}, // avu -> Latn
+    {0x82C00000u, 17u}, // awa -> Deva
+    {0x86C00000u, 44u}, // awb -> Latn
+    {0xBAC00000u, 44u}, // awo -> Latn
+    {0xDEC00000u, 44u}, // awx -> Latn
+    {0x61790000u, 44u}, // ay -> Latn
+    {0x87000000u, 44u}, // ayb -> Latn
+    {0x617A0000u, 44u}, // az -> Latn
     {0x617A4951u,  1u}, // az-IQ -> Arab
     {0x617A4952u,  1u}, // az-IR -> Arab
-    {0x617A5255u, 15u}, // az-RU -> Cyrl
-    {0x62610000u, 15u}, // ba -> Cyrl
+    {0x617A5255u, 16u}, // az-RU -> Cyrl
+    {0x62610000u, 16u}, // ba -> Cyrl
     {0xAC010000u,  1u}, // bal -> Arab
-    {0xB4010000u, 40u}, // ban -> Latn
-    {0xBC010000u, 16u}, // bap -> Deva
-    {0xC4010000u, 40u}, // bar -> Latn
-    {0xC8010000u, 40u}, // bas -> Latn
-    {0xD4010000u, 40u}, // bav -> Latn
+    {0xB4010000u, 44u}, // ban -> Latn
+    {0xBC010000u, 17u}, // bap -> Deva
+    {0xC4010000u, 44u}, // bar -> Latn
+    {0xC8010000u, 44u}, // bas -> Latn
+    {0xD4010000u, 44u}, // bav -> Latn
     {0xDC010000u,  5u}, // bax -> Bamu
-    {0x80210000u, 40u}, // bba -> Latn
-    {0x84210000u, 40u}, // bbb -> Latn
-    {0x88210000u, 40u}, // bbc -> Latn
-    {0x8C210000u, 40u}, // bbd -> Latn
-    {0xA4210000u, 40u}, // bbj -> Latn
-    {0xBC210000u, 40u}, // bbp -> Latn
-    {0xC4210000u, 40u}, // bbr -> Latn
-    {0x94410000u, 40u}, // bcf -> Latn
-    {0x9C410000u, 40u}, // bch -> Latn
-    {0xA0410000u, 40u}, // bci -> Latn
-    {0xB0410000u, 40u}, // bcm -> Latn
-    {0xB4410000u, 40u}, // bcn -> Latn
-    {0xB8410000u, 40u}, // bco -> Latn
-    {0xC0410000u, 18u}, // bcq -> Ethi
-    {0xD0410000u, 40u}, // bcu -> Latn
-    {0x8C610000u, 40u}, // bdd -> Latn
-    {0x62650000u, 15u}, // be -> Cyrl
-    {0x94810000u, 40u}, // bef -> Latn
-    {0x9C810000u, 40u}, // beh -> Latn
+    {0x80210000u, 44u}, // bba -> Latn
+    {0x84210000u, 44u}, // bbb -> Latn
+    {0x88210000u, 44u}, // bbc -> Latn
+    {0x8C210000u, 44u}, // bbd -> Latn
+    {0xA4210000u, 44u}, // bbj -> Latn
+    {0xBC210000u, 44u}, // bbp -> Latn
+    {0xC4210000u, 44u}, // bbr -> Latn
+    {0x94410000u, 44u}, // bcf -> Latn
+    {0x9C410000u, 44u}, // bch -> Latn
+    {0xA0410000u, 44u}, // bci -> Latn
+    {0xB0410000u, 44u}, // bcm -> Latn
+    {0xB4410000u, 44u}, // bcn -> Latn
+    {0xB8410000u, 44u}, // bco -> Latn
+    {0xC0410000u, 19u}, // bcq -> Ethi
+    {0xD0410000u, 44u}, // bcu -> Latn
+    {0x8C610000u, 44u}, // bdd -> Latn
+    {0x62650000u, 16u}, // be -> Cyrl
+    {0x94810000u, 44u}, // bef -> Latn
+    {0x9C810000u, 44u}, // beh -> Latn
     {0xA4810000u,  1u}, // bej -> Arab
-    {0xB0810000u, 40u}, // bem -> Latn
-    {0xCC810000u, 40u}, // bet -> Latn
-    {0xD8810000u, 40u}, // bew -> Latn
-    {0xDC810000u, 40u}, // bex -> Latn
-    {0xE4810000u, 40u}, // bez -> Latn
-    {0x8CA10000u, 40u}, // bfd -> Latn
-    {0xC0A10000u, 74u}, // bfq -> Taml
+    {0xB0810000u, 44u}, // bem -> Latn
+    {0xCC810000u, 44u}, // bet -> Latn
+    {0xD8810000u, 44u}, // bew -> Latn
+    {0xDC810000u, 44u}, // bex -> Latn
+    {0xE4810000u, 44u}, // bez -> Latn
+    {0x8CA10000u, 44u}, // bfd -> Latn
+    {0xC0A10000u, 81u}, // bfq -> Taml
     {0xCCA10000u,  1u}, // bft -> Arab
-    {0xE0A10000u, 16u}, // bfy -> Deva
-    {0x62670000u, 15u}, // bg -> Cyrl
-    {0x88C10000u, 16u}, // bgc -> Deva
+    {0xE0A10000u, 17u}, // bfy -> Deva
+    {0x62670000u, 16u}, // bg -> Cyrl
+    {0x88C10000u, 17u}, // bgc -> Deva
     {0xB4C10000u,  1u}, // bgn -> Arab
-    {0xDCC10000u, 21u}, // bgx -> Grek
-    {0x84E10000u, 16u}, // bhb -> Deva
-    {0x98E10000u, 40u}, // bhg -> Latn
-    {0xA0E10000u, 16u}, // bhi -> Deva
-    {0xA8E10000u, 40u}, // bhk -> Latn
-    {0xACE10000u, 40u}, // bhl -> Latn
-    {0xB8E10000u, 16u}, // bho -> Deva
-    {0xE0E10000u, 40u}, // bhy -> Latn
-    {0x62690000u, 40u}, // bi -> Latn
-    {0x85010000u, 40u}, // bib -> Latn
-    {0x99010000u, 40u}, // big -> Latn
-    {0xA9010000u, 40u}, // bik -> Latn
-    {0xB1010000u, 40u}, // bim -> Latn
-    {0xB5010000u, 40u}, // bin -> Latn
-    {0xB9010000u, 40u}, // bio -> Latn
-    {0xC1010000u, 40u}, // biq -> Latn
-    {0x9D210000u, 40u}, // bjh -> Latn
-    {0xA1210000u, 18u}, // bji -> Ethi
-    {0xA5210000u, 16u}, // bjj -> Deva
-    {0xB5210000u, 40u}, // bjn -> Latn
-    {0xB9210000u, 40u}, // bjo -> Latn
-    {0xC5210000u, 40u}, // bjr -> Latn
-    {0xE5210000u, 40u}, // bjz -> Latn
-    {0x89410000u, 40u}, // bkc -> Latn
-    {0xB1410000u, 40u}, // bkm -> Latn
-    {0xC1410000u, 40u}, // bkq -> Latn
-    {0xD1410000u, 40u}, // bku -> Latn
-    {0xD5410000u, 40u}, // bkv -> Latn
-    {0xCD610000u, 76u}, // blt -> Tavt
-    {0x626D0000u, 40u}, // bm -> Latn
-    {0x9D810000u, 40u}, // bmh -> Latn
-    {0xA9810000u, 40u}, // bmk -> Latn
-    {0xC1810000u, 40u}, // bmq -> Latn
-    {0xD1810000u, 40u}, // bmu -> Latn
+    {0xDCC10000u, 24u}, // bgx -> Grek
+    {0x84E10000u, 17u}, // bhb -> Deva
+    {0x98E10000u, 44u}, // bhg -> Latn
+    {0xA0E10000u, 17u}, // bhi -> Deva
+    {0xA8E10000u, 44u}, // bhk -> Latn
+    {0xACE10000u, 44u}, // bhl -> Latn
+    {0xB8E10000u, 17u}, // bho -> Deva
+    {0xE0E10000u, 44u}, // bhy -> Latn
+    {0x62690000u, 44u}, // bi -> Latn
+    {0x85010000u, 44u}, // bib -> Latn
+    {0x99010000u, 44u}, // big -> Latn
+    {0xA9010000u, 44u}, // bik -> Latn
+    {0xB1010000u, 44u}, // bim -> Latn
+    {0xB5010000u, 44u}, // bin -> Latn
+    {0xB9010000u, 44u}, // bio -> Latn
+    {0xC1010000u, 44u}, // biq -> Latn
+    {0x9D210000u, 44u}, // bjh -> Latn
+    {0xA1210000u, 19u}, // bji -> Ethi
+    {0xA5210000u, 17u}, // bjj -> Deva
+    {0xB5210000u, 44u}, // bjn -> Latn
+    {0xB9210000u, 44u}, // bjo -> Latn
+    {0xC5210000u, 44u}, // bjr -> Latn
+    {0xCD210000u, 44u}, // bjt -> Latn
+    {0xE5210000u, 44u}, // bjz -> Latn
+    {0x89410000u, 44u}, // bkc -> Latn
+    {0xB1410000u, 44u}, // bkm -> Latn
+    {0xC1410000u, 44u}, // bkq -> Latn
+    {0xD1410000u, 44u}, // bku -> Latn
+    {0xD5410000u, 44u}, // bkv -> Latn
+    {0xCD610000u, 83u}, // blt -> Tavt
+    {0x626D0000u, 44u}, // bm -> Latn
+    {0x9D810000u, 44u}, // bmh -> Latn
+    {0xA9810000u, 44u}, // bmk -> Latn
+    {0xC1810000u, 44u}, // bmq -> Latn
+    {0xD1810000u, 44u}, // bmu -> Latn
     {0x626E0000u,  7u}, // bn -> Beng
-    {0x99A10000u, 40u}, // bng -> Latn
-    {0xB1A10000u, 40u}, // bnm -> Latn
-    {0xBDA10000u, 40u}, // bnp -> Latn
-    {0x626F0000u, 81u}, // bo -> Tibt
-    {0xA5C10000u, 40u}, // boj -> Latn
-    {0xB1C10000u, 40u}, // bom -> Latn
-    {0xB5C10000u, 40u}, // bon -> Latn
+    {0x99A10000u, 44u}, // bng -> Latn
+    {0xB1A10000u, 44u}, // bnm -> Latn
+    {0xBDA10000u, 44u}, // bnp -> Latn
+    {0x626F0000u, 88u}, // bo -> Tibt
+    {0xA5C10000u, 44u}, // boj -> Latn
+    {0xB1C10000u, 44u}, // bom -> Latn
+    {0xB5C10000u, 44u}, // bon -> Latn
     {0xE1E10000u,  7u}, // bpy -> Beng
-    {0x8A010000u, 40u}, // bqc -> Latn
+    {0x8A010000u, 44u}, // bqc -> Latn
     {0xA2010000u,  1u}, // bqi -> Arab
-    {0xBE010000u, 40u}, // bqp -> Latn
-    {0xD6010000u, 40u}, // bqv -> Latn
-    {0x62720000u, 40u}, // br -> Latn
-    {0x82210000u, 16u}, // bra -> Deva
+    {0xBE010000u, 44u}, // bqp -> Latn
+    {0xD6010000u, 44u}, // bqv -> Latn
+    {0x62720000u, 44u}, // br -> Latn
+    {0x82210000u, 17u}, // bra -> Deva
     {0x9E210000u,  1u}, // brh -> Arab
-    {0xDE210000u, 16u}, // brx -> Deva
-    {0xE6210000u, 40u}, // brz -> Latn
-    {0x62730000u, 40u}, // bs -> Latn
-    {0xA6410000u, 40u}, // bsj -> Latn
+    {0xDE210000u, 17u}, // brx -> Deva
+    {0xE6210000u, 44u}, // brz -> Latn
+    {0x62730000u, 44u}, // bs -> Latn
+    {0xA6410000u, 44u}, // bsj -> Latn
     {0xC2410000u,  6u}, // bsq -> Bass
-    {0xCA410000u, 40u}, // bss -> Latn
-    {0xCE410000u, 18u}, // bst -> Ethi
-    {0xBA610000u, 40u}, // bto -> Latn
-    {0xCE610000u, 40u}, // btt -> Latn
-    {0xD6610000u, 16u}, // btv -> Deva
-    {0x82810000u, 15u}, // bua -> Cyrl
-    {0x8A810000u, 40u}, // buc -> Latn
-    {0x8E810000u, 40u}, // bud -> Latn
-    {0x9A810000u, 40u}, // bug -> Latn
-    {0xAA810000u, 40u}, // buk -> Latn
-    {0xB2810000u, 40u}, // bum -> Latn
-    {0xBA810000u, 40u}, // buo -> Latn
-    {0xCA810000u, 40u}, // bus -> Latn
-    {0xD2810000u, 40u}, // buu -> Latn
-    {0x86A10000u, 40u}, // bvb -> Latn
-    {0x8EC10000u, 40u}, // bwd -> Latn
-    {0xC6C10000u, 40u}, // bwr -> Latn
-    {0x9EE10000u, 40u}, // bxh -> Latn
-    {0x93010000u, 40u}, // bye -> Latn
-    {0xB7010000u, 18u}, // byn -> Ethi
-    {0xC7010000u, 40u}, // byr -> Latn
-    {0xCB010000u, 40u}, // bys -> Latn
-    {0xD7010000u, 40u}, // byv -> Latn
-    {0xDF010000u, 40u}, // byx -> Latn
-    {0x83210000u, 40u}, // bza -> Latn
-    {0x93210000u, 40u}, // bze -> Latn
-    {0x97210000u, 40u}, // bzf -> Latn
-    {0x9F210000u, 40u}, // bzh -> Latn
-    {0xDB210000u, 40u}, // bzw -> Latn
-    {0x63610000u, 40u}, // ca -> Latn
-    {0xB4020000u, 40u}, // can -> Latn
-    {0xA4220000u, 40u}, // cbj -> Latn
-    {0x9C420000u, 40u}, // cch -> Latn
-    {0xBC420000u,  7u}, // ccp -> Beng
-    {0x63650000u, 15u}, // ce -> Cyrl
-    {0x84820000u, 40u}, // ceb -> Latn
-    {0x80A20000u, 40u}, // cfa -> Latn
-    {0x98C20000u, 40u}, // cgg -> Latn
-    {0x63680000u, 40u}, // ch -> Latn
-    {0xA8E20000u, 40u}, // chk -> Latn
-    {0xB0E20000u, 15u}, // chm -> Cyrl
-    {0xB8E20000u, 40u}, // cho -> Latn
-    {0xBCE20000u, 40u}, // chp -> Latn
-    {0xC4E20000u, 12u}, // chr -> Cher
+    {0xCA410000u, 44u}, // bss -> Latn
+    {0xCE410000u, 19u}, // bst -> Ethi
+    {0xBA610000u, 44u}, // bto -> Latn
+    {0xCE610000u, 44u}, // btt -> Latn
+    {0xD6610000u, 17u}, // btv -> Deva
+    {0x82810000u, 16u}, // bua -> Cyrl
+    {0x8A810000u, 44u}, // buc -> Latn
+    {0x8E810000u, 44u}, // bud -> Latn
+    {0x9A810000u, 44u}, // bug -> Latn
+    {0xAA810000u, 44u}, // buk -> Latn
+    {0xB2810000u, 44u}, // bum -> Latn
+    {0xBA810000u, 44u}, // buo -> Latn
+    {0xCA810000u, 44u}, // bus -> Latn
+    {0xD2810000u, 44u}, // buu -> Latn
+    {0x86A10000u, 44u}, // bvb -> Latn
+    {0x8EC10000u, 44u}, // bwd -> Latn
+    {0xC6C10000u, 44u}, // bwr -> Latn
+    {0x9EE10000u, 44u}, // bxh -> Latn
+    {0x93010000u, 44u}, // bye -> Latn
+    {0xB7010000u, 19u}, // byn -> Ethi
+    {0xC7010000u, 44u}, // byr -> Latn
+    {0xCB010000u, 44u}, // bys -> Latn
+    {0xD7010000u, 44u}, // byv -> Latn
+    {0xDF010000u, 44u}, // byx -> Latn
+    {0x83210000u, 44u}, // bza -> Latn
+    {0x93210000u, 44u}, // bze -> Latn
+    {0x97210000u, 44u}, // bzf -> Latn
+    {0x9F210000u, 44u}, // bzh -> Latn
+    {0xDB210000u, 44u}, // bzw -> Latn
+    {0x63610000u, 44u}, // ca -> Latn
+    {0xB4020000u, 44u}, // can -> Latn
+    {0xA4220000u, 44u}, // cbj -> Latn
+    {0x9C420000u, 44u}, // cch -> Latn
+    {0xBC420000u,  9u}, // ccp -> Cakm
+    {0x63650000u, 16u}, // ce -> Cyrl
+    {0x84820000u, 44u}, // ceb -> Latn
+    {0x80A20000u, 44u}, // cfa -> Latn
+    {0x98C20000u, 44u}, // cgg -> Latn
+    {0x63680000u, 44u}, // ch -> Latn
+    {0xA8E20000u, 44u}, // chk -> Latn
+    {0xB0E20000u, 16u}, // chm -> Cyrl
+    {0xB8E20000u, 44u}, // cho -> Latn
+    {0xBCE20000u, 44u}, // chp -> Latn
+    {0xC4E20000u, 13u}, // chr -> Cher
     {0x81220000u,  1u}, // cja -> Arab
-    {0xB1220000u, 11u}, // cjm -> Cham
-    {0xD5220000u, 40u}, // cjv -> Latn
+    {0xB1220000u, 12u}, // cjm -> Cham
+    {0xD5220000u, 44u}, // cjv -> Latn
     {0x85420000u,  1u}, // ckb -> Arab
-    {0xAD420000u, 40u}, // ckl -> Latn
-    {0xB9420000u, 40u}, // cko -> Latn
-    {0xE1420000u, 40u}, // cky -> Latn
-    {0x81620000u, 40u}, // cla -> Latn
-    {0x91820000u, 40u}, // cme -> Latn
-    {0x636F0000u, 40u}, // co -> Latn
-    {0xBDC20000u, 13u}, // cop -> Copt
-    {0xC9E20000u, 40u}, // cps -> Latn
-    {0x63720000u,  9u}, // cr -> Cans
-    {0xA6220000u,  9u}, // crj -> Cans
-    {0xAA220000u,  9u}, // crk -> Cans
-    {0xAE220000u,  9u}, // crl -> Cans
-    {0xB2220000u,  9u}, // crm -> Cans
-    {0xCA220000u, 40u}, // crs -> Latn
-    {0x63730000u, 40u}, // cs -> Latn
-    {0x86420000u, 40u}, // csb -> Latn
-    {0xDA420000u,  9u}, // csw -> Cans
-    {0x8E620000u, 59u}, // ctd -> Pauc
-    {0x63750000u, 15u}, // cu -> Cyrl
-    {0x63760000u, 15u}, // cv -> Cyrl
-    {0x63790000u, 40u}, // cy -> Latn
-    {0x64610000u, 40u}, // da -> Latn
-    {0x8C030000u, 40u}, // dad -> Latn
-    {0x94030000u, 40u}, // daf -> Latn
-    {0x98030000u, 40u}, // dag -> Latn
-    {0x9C030000u, 40u}, // dah -> Latn
-    {0xA8030000u, 40u}, // dak -> Latn
-    {0xC4030000u, 15u}, // dar -> Cyrl
-    {0xD4030000u, 40u}, // dav -> Latn
-    {0x8C230000u, 40u}, // dbd -> Latn
-    {0xC0230000u, 40u}, // dbq -> Latn
+    {0xAD420000u, 44u}, // ckl -> Latn
+    {0xB9420000u, 44u}, // cko -> Latn
+    {0xE1420000u, 44u}, // cky -> Latn
+    {0x81620000u, 44u}, // cla -> Latn
+    {0x91820000u, 44u}, // cme -> Latn
+    {0x99820000u, 77u}, // cmg -> Soyo
+    {0x636F0000u, 44u}, // co -> Latn
+    {0xBDC20000u, 14u}, // cop -> Copt
+    {0xC9E20000u, 44u}, // cps -> Latn
+    {0x63720000u, 10u}, // cr -> Cans
+    {0x9E220000u, 16u}, // crh -> Cyrl
+    {0xA6220000u, 10u}, // crj -> Cans
+    {0xAA220000u, 10u}, // crk -> Cans
+    {0xAE220000u, 10u}, // crl -> Cans
+    {0xB2220000u, 10u}, // crm -> Cans
+    {0xCA220000u, 44u}, // crs -> Latn
+    {0x63730000u, 44u}, // cs -> Latn
+    {0x86420000u, 44u}, // csb -> Latn
+    {0xDA420000u, 10u}, // csw -> Cans
+    {0x8E620000u, 64u}, // ctd -> Pauc
+    {0x63750000u, 16u}, // cu -> Cyrl
+    {0x63760000u, 16u}, // cv -> Cyrl
+    {0x63790000u, 44u}, // cy -> Latn
+    {0x64610000u, 44u}, // da -> Latn
+    {0x8C030000u, 44u}, // dad -> Latn
+    {0x94030000u, 44u}, // daf -> Latn
+    {0x98030000u, 44u}, // dag -> Latn
+    {0x9C030000u, 44u}, // dah -> Latn
+    {0xA8030000u, 44u}, // dak -> Latn
+    {0xC4030000u, 16u}, // dar -> Cyrl
+    {0xD4030000u, 44u}, // dav -> Latn
+    {0x8C230000u, 44u}, // dbd -> Latn
+    {0xC0230000u, 44u}, // dbq -> Latn
     {0x88430000u,  1u}, // dcc -> Arab
-    {0xB4630000u, 40u}, // ddn -> Latn
-    {0x64650000u, 40u}, // de -> Latn
-    {0x8C830000u, 40u}, // ded -> Latn
-    {0xB4830000u, 40u}, // den -> Latn
-    {0x80C30000u, 40u}, // dga -> Latn
-    {0x9CC30000u, 40u}, // dgh -> Latn
-    {0xA0C30000u, 40u}, // dgi -> Latn
+    {0xB4630000u, 44u}, // ddn -> Latn
+    {0x64650000u, 44u}, // de -> Latn
+    {0x8C830000u, 44u}, // ded -> Latn
+    {0xB4830000u, 44u}, // den -> Latn
+    {0x80C30000u, 44u}, // dga -> Latn
+    {0x9CC30000u, 44u}, // dgh -> Latn
+    {0xA0C30000u, 44u}, // dgi -> Latn
     {0xACC30000u,  1u}, // dgl -> Arab
-    {0xC4C30000u, 40u}, // dgr -> Latn
-    {0xE4C30000u, 40u}, // dgz -> Latn
-    {0x81030000u, 40u}, // dia -> Latn
-    {0x91230000u, 40u}, // dje -> Latn
-    {0xA5A30000u, 40u}, // dnj -> Latn
-    {0x85C30000u, 40u}, // dob -> Latn
+    {0xC4C30000u, 44u}, // dgr -> Latn
+    {0xE4C30000u, 44u}, // dgz -> Latn
+    {0x81030000u, 44u}, // dia -> Latn
+    {0x91230000u, 44u}, // dje -> Latn
+    {0xA5A30000u, 44u}, // dnj -> Latn
+    {0x85C30000u, 44u}, // dob -> Latn
     {0xA1C30000u,  1u}, // doi -> Arab
-    {0xBDC30000u, 40u}, // dop -> Latn
-    {0xD9C30000u, 40u}, // dow -> Latn
-    {0xA2230000u, 40u}, // dri -> Latn
-    {0xCA230000u, 18u}, // drs -> Ethi
-    {0x86430000u, 40u}, // dsb -> Latn
-    {0xB2630000u, 40u}, // dtm -> Latn
-    {0xBE630000u, 40u}, // dtp -> Latn
-    {0xCA630000u, 40u}, // dts -> Latn
-    {0xE2630000u, 16u}, // dty -> Deva
-    {0x82830000u, 40u}, // dua -> Latn
-    {0x8A830000u, 40u}, // duc -> Latn
-    {0x8E830000u, 40u}, // dud -> Latn
-    {0x9A830000u, 40u}, // dug -> Latn
-    {0x64760000u, 79u}, // dv -> Thaa
-    {0x82A30000u, 40u}, // dva -> Latn
-    {0xDAC30000u, 40u}, // dww -> Latn
-    {0xBB030000u, 40u}, // dyo -> Latn
-    {0xD3030000u, 40u}, // dyu -> Latn
-    {0x647A0000u, 81u}, // dz -> Tibt
-    {0x9B230000u, 40u}, // dzg -> Latn
-    {0xD0240000u, 40u}, // ebu -> Latn
-    {0x65650000u, 40u}, // ee -> Latn
-    {0xA0A40000u, 40u}, // efi -> Latn
-    {0xACC40000u, 40u}, // egl -> Latn
-    {0xE0C40000u, 17u}, // egy -> Egyp
-    {0xE1440000u, 32u}, // eky -> Kali
-    {0x656C0000u, 21u}, // el -> Grek
-    {0x81840000u, 40u}, // ema -> Latn
-    {0xA1840000u, 40u}, // emi -> Latn
-    {0x656E0000u, 40u}, // en -> Latn
-    {0x656E5841u, 87u}, // en-XA -> ~~~A
-    {0xB5A40000u, 40u}, // enn -> Latn
-    {0xC1A40000u, 40u}, // enq -> Latn
-    {0x656F0000u, 40u}, // eo -> Latn
-    {0xA2240000u, 40u}, // eri -> Latn
-    {0x65730000u, 40u}, // es -> Latn
-    {0xD2440000u, 40u}, // esu -> Latn
-    {0x65740000u, 40u}, // et -> Latn
-    {0xC6640000u, 40u}, // etr -> Latn
-    {0xCE640000u, 30u}, // ett -> Ital
-    {0xD2640000u, 40u}, // etu -> Latn
-    {0xDE640000u, 40u}, // etx -> Latn
-    {0x65750000u, 40u}, // eu -> Latn
-    {0xBAC40000u, 40u}, // ewo -> Latn
-    {0xCEE40000u, 40u}, // ext -> Latn
+    {0xBDC30000u, 44u}, // dop -> Latn
+    {0xD9C30000u, 44u}, // dow -> Latn
+    {0xA2230000u, 44u}, // dri -> Latn
+    {0xCA230000u, 19u}, // drs -> Ethi
+    {0x86430000u, 44u}, // dsb -> Latn
+    {0xB2630000u, 44u}, // dtm -> Latn
+    {0xBE630000u, 44u}, // dtp -> Latn
+    {0xCA630000u, 44u}, // dts -> Latn
+    {0xE2630000u, 17u}, // dty -> Deva
+    {0x82830000u, 44u}, // dua -> Latn
+    {0x8A830000u, 44u}, // duc -> Latn
+    {0x8E830000u, 44u}, // dud -> Latn
+    {0x9A830000u, 44u}, // dug -> Latn
+    {0x64760000u, 86u}, // dv -> Thaa
+    {0x82A30000u, 44u}, // dva -> Latn
+    {0xDAC30000u, 44u}, // dww -> Latn
+    {0xBB030000u, 44u}, // dyo -> Latn
+    {0xD3030000u, 44u}, // dyu -> Latn
+    {0x647A0000u, 88u}, // dz -> Tibt
+    {0x9B230000u, 44u}, // dzg -> Latn
+    {0xD0240000u, 44u}, // ebu -> Latn
+    {0x65650000u, 44u}, // ee -> Latn
+    {0xA0A40000u, 44u}, // efi -> Latn
+    {0xACC40000u, 44u}, // egl -> Latn
+    {0xE0C40000u, 18u}, // egy -> Egyp
+    {0x81440000u, 44u}, // eka -> Latn
+    {0xE1440000u, 36u}, // eky -> Kali
+    {0x656C0000u, 24u}, // el -> Grek
+    {0x81840000u, 44u}, // ema -> Latn
+    {0xA1840000u, 44u}, // emi -> Latn
+    {0x656E0000u, 44u}, // en -> Latn
+    {0x656E5841u, 95u}, // en-XA -> ~~~A
+    {0xB5A40000u, 44u}, // enn -> Latn
+    {0xC1A40000u, 44u}, // enq -> Latn
+    {0x656F0000u, 44u}, // eo -> Latn
+    {0xA2240000u, 44u}, // eri -> Latn
+    {0x65730000u, 44u}, // es -> Latn
+    {0x9A440000u, 22u}, // esg -> Gonm
+    {0xD2440000u, 44u}, // esu -> Latn
+    {0x65740000u, 44u}, // et -> Latn
+    {0xC6640000u, 44u}, // etr -> Latn
+    {0xCE640000u, 34u}, // ett -> Ital
+    {0xD2640000u, 44u}, // etu -> Latn
+    {0xDE640000u, 44u}, // etx -> Latn
+    {0x65750000u, 44u}, // eu -> Latn
+    {0xBAC40000u, 44u}, // ewo -> Latn
+    {0xCEE40000u, 44u}, // ext -> Latn
     {0x66610000u,  1u}, // fa -> Arab
-    {0x80050000u, 40u}, // faa -> Latn
-    {0x84050000u, 40u}, // fab -> Latn
-    {0x98050000u, 40u}, // fag -> Latn
-    {0xA0050000u, 40u}, // fai -> Latn
-    {0xB4050000u, 40u}, // fan -> Latn
-    {0x66660000u, 40u}, // ff -> Latn
-    {0xA0A50000u, 40u}, // ffi -> Latn
-    {0xB0A50000u, 40u}, // ffm -> Latn
-    {0x66690000u, 40u}, // fi -> Latn
+    {0x80050000u, 44u}, // faa -> Latn
+    {0x84050000u, 44u}, // fab -> Latn
+    {0x98050000u, 44u}, // fag -> Latn
+    {0xA0050000u, 44u}, // fai -> Latn
+    {0xB4050000u, 44u}, // fan -> Latn
+    {0x66660000u, 44u}, // ff -> Latn
+    {0xA0A50000u, 44u}, // ffi -> Latn
+    {0xB0A50000u, 44u}, // ffm -> Latn
+    {0x66690000u, 44u}, // fi -> Latn
     {0x81050000u,  1u}, // fia -> Arab
-    {0xAD050000u, 40u}, // fil -> Latn
-    {0xCD050000u, 40u}, // fit -> Latn
-    {0x666A0000u, 40u}, // fj -> Latn
-    {0xC5650000u, 40u}, // flr -> Latn
-    {0xBD850000u, 40u}, // fmp -> Latn
-    {0x666F0000u, 40u}, // fo -> Latn
-    {0x8DC50000u, 40u}, // fod -> Latn
-    {0xB5C50000u, 40u}, // fon -> Latn
-    {0xC5C50000u, 40u}, // for -> Latn
-    {0x91E50000u, 40u}, // fpe -> Latn
-    {0xCA050000u, 40u}, // fqs -> Latn
-    {0x66720000u, 40u}, // fr -> Latn
-    {0x8A250000u, 40u}, // frc -> Latn
-    {0xBE250000u, 40u}, // frp -> Latn
-    {0xC6250000u, 40u}, // frr -> Latn
-    {0xCA250000u, 40u}, // frs -> Latn
+    {0xAD050000u, 44u}, // fil -> Latn
+    {0xCD050000u, 44u}, // fit -> Latn
+    {0x666A0000u, 44u}, // fj -> Latn
+    {0xC5650000u, 44u}, // flr -> Latn
+    {0xBD850000u, 44u}, // fmp -> Latn
+    {0x666F0000u, 44u}, // fo -> Latn
+    {0x8DC50000u, 44u}, // fod -> Latn
+    {0xB5C50000u, 44u}, // fon -> Latn
+    {0xC5C50000u, 44u}, // for -> Latn
+    {0x91E50000u, 44u}, // fpe -> Latn
+    {0xCA050000u, 44u}, // fqs -> Latn
+    {0x66720000u, 44u}, // fr -> Latn
+    {0x8A250000u, 44u}, // frc -> Latn
+    {0xBE250000u, 44u}, // frp -> Latn
+    {0xC6250000u, 44u}, // frr -> Latn
+    {0xCA250000u, 44u}, // frs -> Latn
     {0x86850000u,  1u}, // fub -> Arab
-    {0x8E850000u, 40u}, // fud -> Latn
-    {0x92850000u, 40u}, // fue -> Latn
-    {0x96850000u, 40u}, // fuf -> Latn
-    {0x9E850000u, 40u}, // fuh -> Latn
-    {0xC2850000u, 40u}, // fuq -> Latn
-    {0xC6850000u, 40u}, // fur -> Latn
-    {0xD6850000u, 40u}, // fuv -> Latn
-    {0xE2850000u, 40u}, // fuy -> Latn
-    {0xC6A50000u, 40u}, // fvr -> Latn
-    {0x66790000u, 40u}, // fy -> Latn
-    {0x67610000u, 40u}, // ga -> Latn
-    {0x80060000u, 40u}, // gaa -> Latn
-    {0x94060000u, 40u}, // gaf -> Latn
-    {0x98060000u, 40u}, // gag -> Latn
-    {0x9C060000u, 40u}, // gah -> Latn
-    {0xA4060000u, 40u}, // gaj -> Latn
-    {0xB0060000u, 40u}, // gam -> Latn
-    {0xB4060000u, 24u}, // gan -> Hans
-    {0xD8060000u, 40u}, // gaw -> Latn
-    {0xE0060000u, 40u}, // gay -> Latn
-    {0x94260000u, 40u}, // gbf -> Latn
-    {0xB0260000u, 16u}, // gbm -> Deva
-    {0xE0260000u, 40u}, // gby -> Latn
+    {0x8E850000u, 44u}, // fud -> Latn
+    {0x92850000u, 44u}, // fue -> Latn
+    {0x96850000u, 44u}, // fuf -> Latn
+    {0x9E850000u, 44u}, // fuh -> Latn
+    {0xC2850000u, 44u}, // fuq -> Latn
+    {0xC6850000u, 44u}, // fur -> Latn
+    {0xD6850000u, 44u}, // fuv -> Latn
+    {0xE2850000u, 44u}, // fuy -> Latn
+    {0xC6A50000u, 44u}, // fvr -> Latn
+    {0x66790000u, 44u}, // fy -> Latn
+    {0x67610000u, 44u}, // ga -> Latn
+    {0x80060000u, 44u}, // gaa -> Latn
+    {0x94060000u, 44u}, // gaf -> Latn
+    {0x98060000u, 44u}, // gag -> Latn
+    {0x9C060000u, 44u}, // gah -> Latn
+    {0xA4060000u, 44u}, // gaj -> Latn
+    {0xB0060000u, 44u}, // gam -> Latn
+    {0xB4060000u, 27u}, // gan -> Hans
+    {0xD8060000u, 44u}, // gaw -> Latn
+    {0xE0060000u, 44u}, // gay -> Latn
+    {0x80260000u, 44u}, // gba -> Latn
+    {0x94260000u, 44u}, // gbf -> Latn
+    {0xB0260000u, 17u}, // gbm -> Deva
+    {0xE0260000u, 44u}, // gby -> Latn
     {0xE4260000u,  1u}, // gbz -> Arab
-    {0xC4460000u, 40u}, // gcr -> Latn
-    {0x67640000u, 40u}, // gd -> Latn
-    {0x90660000u, 40u}, // gde -> Latn
-    {0xB4660000u, 40u}, // gdn -> Latn
-    {0xC4660000u, 40u}, // gdr -> Latn
-    {0x84860000u, 40u}, // geb -> Latn
-    {0xA4860000u, 40u}, // gej -> Latn
-    {0xAC860000u, 40u}, // gel -> Latn
-    {0xE4860000u, 18u}, // gez -> Ethi
-    {0xA8A60000u, 40u}, // gfk -> Latn
-    {0xB4C60000u, 16u}, // ggn -> Deva
-    {0xC8E60000u, 40u}, // ghs -> Latn
-    {0xAD060000u, 40u}, // gil -> Latn
-    {0xB1060000u, 40u}, // gim -> Latn
+    {0xC4460000u, 44u}, // gcr -> Latn
+    {0x67640000u, 44u}, // gd -> Latn
+    {0x90660000u, 44u}, // gde -> Latn
+    {0xB4660000u, 44u}, // gdn -> Latn
+    {0xC4660000u, 44u}, // gdr -> Latn
+    {0x84860000u, 44u}, // geb -> Latn
+    {0xA4860000u, 44u}, // gej -> Latn
+    {0xAC860000u, 44u}, // gel -> Latn
+    {0xE4860000u, 19u}, // gez -> Ethi
+    {0xA8A60000u, 44u}, // gfk -> Latn
+    {0xB4C60000u, 17u}, // ggn -> Deva
+    {0xC8E60000u, 44u}, // ghs -> Latn
+    {0xAD060000u, 44u}, // gil -> Latn
+    {0xB1060000u, 44u}, // gim -> Latn
     {0xA9260000u,  1u}, // gjk -> Arab
-    {0xB5260000u, 40u}, // gjn -> Latn
+    {0xB5260000u, 44u}, // gjn -> Latn
     {0xD1260000u,  1u}, // gju -> Arab
-    {0xB5460000u, 40u}, // gkn -> Latn
-    {0xBD460000u, 40u}, // gkp -> Latn
-    {0x676C0000u, 40u}, // gl -> Latn
+    {0xB5460000u, 44u}, // gkn -> Latn
+    {0xBD460000u, 44u}, // gkp -> Latn
+    {0x676C0000u, 44u}, // gl -> Latn
     {0xA9660000u,  1u}, // glk -> Arab
-    {0xB1860000u, 40u}, // gmm -> Latn
-    {0xD5860000u, 18u}, // gmv -> Ethi
-    {0x676E0000u, 40u}, // gn -> Latn
-    {0x8DA60000u, 40u}, // gnd -> Latn
-    {0x99A60000u, 40u}, // gng -> Latn
-    {0x8DC60000u, 40u}, // god -> Latn
-    {0x95C60000u, 18u}, // gof -> Ethi
-    {0xA1C60000u, 40u}, // goi -> Latn
-    {0xB1C60000u, 16u}, // gom -> Deva
-    {0xB5C60000u, 77u}, // gon -> Telu
-    {0xC5C60000u, 40u}, // gor -> Latn
-    {0xC9C60000u, 40u}, // gos -> Latn
-    {0xCDC60000u, 20u}, // got -> Goth
-    {0x8A260000u, 14u}, // grc -> Cprt
+    {0xB1860000u, 44u}, // gmm -> Latn
+    {0xD5860000u, 19u}, // gmv -> Ethi
+    {0x676E0000u, 44u}, // gn -> Latn
+    {0x8DA60000u, 44u}, // gnd -> Latn
+    {0x99A60000u, 44u}, // gng -> Latn
+    {0x8DC60000u, 44u}, // god -> Latn
+    {0x95C60000u, 19u}, // gof -> Ethi
+    {0xA1C60000u, 44u}, // goi -> Latn
+    {0xB1C60000u, 17u}, // gom -> Deva
+    {0xB5C60000u, 84u}, // gon -> Telu
+    {0xC5C60000u, 44u}, // gor -> Latn
+    {0xC9C60000u, 44u}, // gos -> Latn
+    {0xCDC60000u, 23u}, // got -> Goth
+    {0x86260000u, 44u}, // grb -> Latn
+    {0x8A260000u, 15u}, // grc -> Cprt
     {0xCE260000u,  7u}, // grt -> Beng
-    {0xDA260000u, 40u}, // grw -> Latn
-    {0xDA460000u, 40u}, // gsw -> Latn
-    {0x67750000u, 22u}, // gu -> Gujr
-    {0x86860000u, 40u}, // gub -> Latn
-    {0x8A860000u, 40u}, // guc -> Latn
-    {0x8E860000u, 40u}, // gud -> Latn
-    {0xC6860000u, 40u}, // gur -> Latn
-    {0xDA860000u, 40u}, // guw -> Latn
-    {0xDE860000u, 40u}, // gux -> Latn
-    {0xE6860000u, 40u}, // guz -> Latn
-    {0x67760000u, 40u}, // gv -> Latn
-    {0x96A60000u, 40u}, // gvf -> Latn
-    {0xC6A60000u, 16u}, // gvr -> Deva
-    {0xCAA60000u, 40u}, // gvs -> Latn
+    {0xDA260000u, 44u}, // grw -> Latn
+    {0xDA460000u, 44u}, // gsw -> Latn
+    {0x67750000u, 25u}, // gu -> Gujr
+    {0x86860000u, 44u}, // gub -> Latn
+    {0x8A860000u, 44u}, // guc -> Latn
+    {0x8E860000u, 44u}, // gud -> Latn
+    {0xC6860000u, 44u}, // gur -> Latn
+    {0xDA860000u, 44u}, // guw -> Latn
+    {0xDE860000u, 44u}, // gux -> Latn
+    {0xE6860000u, 44u}, // guz -> Latn
+    {0x67760000u, 44u}, // gv -> Latn
+    {0x96A60000u, 44u}, // gvf -> Latn
+    {0xC6A60000u, 17u}, // gvr -> Deva
+    {0xCAA60000u, 44u}, // gvs -> Latn
     {0x8AC60000u,  1u}, // gwc -> Arab
-    {0xA2C60000u, 40u}, // gwi -> Latn
+    {0xA2C60000u, 44u}, // gwi -> Latn
     {0xCEC60000u,  1u}, // gwt -> Arab
-    {0xA3060000u, 40u}, // gyi -> Latn
-    {0x68610000u, 40u}, // ha -> Latn
+    {0xA3060000u, 44u}, // gyi -> Latn
+    {0x68610000u, 44u}, // ha -> Latn
     {0x6861434Du,  1u}, // ha-CM -> Arab
     {0x68615344u,  1u}, // ha-SD -> Arab
-    {0x98070000u, 40u}, // hag -> Latn
-    {0xA8070000u, 24u}, // hak -> Hans
-    {0xB0070000u, 40u}, // ham -> Latn
-    {0xD8070000u, 40u}, // haw -> Latn
+    {0x98070000u, 44u}, // hag -> Latn
+    {0xA8070000u, 27u}, // hak -> Hans
+    {0xB0070000u, 44u}, // ham -> Latn
+    {0xD8070000u, 44u}, // haw -> Latn
     {0xE4070000u,  1u}, // haz -> Arab
-    {0x84270000u, 40u}, // hbb -> Latn
-    {0xE0670000u, 18u}, // hdy -> Ethi
-    {0x68650000u, 27u}, // he -> Hebr
-    {0xE0E70000u, 40u}, // hhy -> Latn
-    {0x68690000u, 16u}, // hi -> Deva
-    {0x81070000u, 40u}, // hia -> Latn
-    {0x95070000u, 40u}, // hif -> Latn
-    {0x99070000u, 40u}, // hig -> Latn
-    {0x9D070000u, 40u}, // hih -> Latn
-    {0xAD070000u, 40u}, // hil -> Latn
-    {0x81670000u, 40u}, // hla -> Latn
-    {0xD1670000u, 28u}, // hlu -> Hluw
-    {0x8D870000u, 62u}, // hmd -> Plrd
-    {0xCD870000u, 40u}, // hmt -> Latn
+    {0x84270000u, 44u}, // hbb -> Latn
+    {0xE0670000u, 19u}, // hdy -> Ethi
+    {0x68650000u, 30u}, // he -> Hebr
+    {0xE0E70000u, 44u}, // hhy -> Latn
+    {0x68690000u, 17u}, // hi -> Deva
+    {0x81070000u, 44u}, // hia -> Latn
+    {0x95070000u, 44u}, // hif -> Latn
+    {0x99070000u, 44u}, // hig -> Latn
+    {0x9D070000u, 44u}, // hih -> Latn
+    {0xAD070000u, 44u}, // hil -> Latn
+    {0x81670000u, 44u}, // hla -> Latn
+    {0xD1670000u, 31u}, // hlu -> Hluw
+    {0x8D870000u, 67u}, // hmd -> Plrd
+    {0xCD870000u, 44u}, // hmt -> Latn
     {0x8DA70000u,  1u}, // hnd -> Arab
-    {0x91A70000u, 16u}, // hne -> Deva
-    {0xA5A70000u, 29u}, // hnj -> Hmng
-    {0xB5A70000u, 40u}, // hnn -> Latn
+    {0x91A70000u, 17u}, // hne -> Deva
+    {0xA5A70000u, 32u}, // hnj -> Hmng
+    {0xB5A70000u, 44u}, // hnn -> Latn
     {0xB9A70000u,  1u}, // hno -> Arab
-    {0x686F0000u, 40u}, // ho -> Latn
-    {0x89C70000u, 16u}, // hoc -> Deva
-    {0xA5C70000u, 16u}, // hoj -> Deva
-    {0xCDC70000u, 40u}, // hot -> Latn
-    {0x68720000u, 40u}, // hr -> Latn
-    {0x86470000u, 40u}, // hsb -> Latn
-    {0xB6470000u, 24u}, // hsn -> Hans
-    {0x68740000u, 40u}, // ht -> Latn
-    {0x68750000u, 40u}, // hu -> Latn
-    {0xA2870000u, 40u}, // hui -> Latn
+    {0x686F0000u, 44u}, // ho -> Latn
+    {0x89C70000u, 17u}, // hoc -> Deva
+    {0xA5C70000u, 17u}, // hoj -> Deva
+    {0xCDC70000u, 44u}, // hot -> Latn
+    {0x68720000u, 44u}, // hr -> Latn
+    {0x86470000u, 44u}, // hsb -> Latn
+    {0xB6470000u, 27u}, // hsn -> Hans
+    {0x68740000u, 44u}, // ht -> Latn
+    {0x68750000u, 44u}, // hu -> Latn
+    {0xA2870000u, 44u}, // hui -> Latn
     {0x68790000u,  3u}, // hy -> Armn
-    {0x687A0000u, 40u}, // hz -> Latn
-    {0x69610000u, 40u}, // ia -> Latn
-    {0xB4080000u, 40u}, // ian -> Latn
-    {0xC4080000u, 40u}, // iar -> Latn
-    {0x80280000u, 40u}, // iba -> Latn
-    {0x84280000u, 40u}, // ibb -> Latn
-    {0xE0280000u, 40u}, // iby -> Latn
-    {0x80480000u, 40u}, // ica -> Latn
-    {0x9C480000u, 40u}, // ich -> Latn
-    {0x69640000u, 40u}, // id -> Latn
-    {0x8C680000u, 40u}, // idd -> Latn
-    {0xA0680000u, 40u}, // idi -> Latn
-    {0xD0680000u, 40u}, // idu -> Latn
-    {0x69670000u, 40u}, // ig -> Latn
-    {0x84C80000u, 40u}, // igb -> Latn
-    {0x90C80000u, 40u}, // ige -> Latn
-    {0x69690000u, 86u}, // ii -> Yiii
-    {0xA5280000u, 40u}, // ijj -> Latn
-    {0x696B0000u, 40u}, // ik -> Latn
-    {0xA9480000u, 40u}, // ikk -> Latn
-    {0xCD480000u, 40u}, // ikt -> Latn
-    {0xD9480000u, 40u}, // ikw -> Latn
-    {0xDD480000u, 40u}, // ikx -> Latn
-    {0xB9680000u, 40u}, // ilo -> Latn
-    {0xB9880000u, 40u}, // imo -> Latn
-    {0x696E0000u, 40u}, // in -> Latn
-    {0x9DA80000u, 15u}, // inh -> Cyrl
-    {0xD1C80000u, 40u}, // iou -> Latn
-    {0xA2280000u, 40u}, // iri -> Latn
-    {0x69730000u, 40u}, // is -> Latn
-    {0x69740000u, 40u}, // it -> Latn
-    {0x69750000u,  9u}, // iu -> Cans
-    {0x69770000u, 27u}, // iw -> Hebr
-    {0xB2C80000u, 40u}, // iwm -> Latn
-    {0xCAC80000u, 40u}, // iws -> Latn
-    {0x9F280000u, 40u}, // izh -> Latn
-    {0xA3280000u, 40u}, // izi -> Latn
-    {0x6A610000u, 31u}, // ja -> Jpan
-    {0x84090000u, 40u}, // jab -> Latn
-    {0xB0090000u, 40u}, // jam -> Latn
-    {0xD0290000u, 40u}, // jbu -> Latn
-    {0xB4890000u, 40u}, // jen -> Latn
-    {0xA8C90000u, 40u}, // jgk -> Latn
-    {0xB8C90000u, 40u}, // jgo -> Latn
-    {0x6A690000u, 27u}, // ji -> Hebr
-    {0x85090000u, 40u}, // jib -> Latn
-    {0x89890000u, 40u}, // jmc -> Latn
-    {0xAD890000u, 16u}, // jml -> Deva
-    {0x82290000u, 40u}, // jra -> Latn
-    {0xCE890000u, 40u}, // jut -> Latn
-    {0x6A760000u, 40u}, // jv -> Latn
-    {0x6A770000u, 40u}, // jw -> Latn
-    {0x6B610000u, 19u}, // ka -> Geor
-    {0x800A0000u, 15u}, // kaa -> Cyrl
-    {0x840A0000u, 40u}, // kab -> Latn
-    {0x880A0000u, 40u}, // kac -> Latn
-    {0x8C0A0000u, 40u}, // kad -> Latn
-    {0xA00A0000u, 40u}, // kai -> Latn
-    {0xA40A0000u, 40u}, // kaj -> Latn
-    {0xB00A0000u, 40u}, // kam -> Latn
-    {0xB80A0000u, 40u}, // kao -> Latn
-    {0x8C2A0000u, 15u}, // kbd -> Cyrl
-    {0xB02A0000u, 40u}, // kbm -> Latn
-    {0xBC2A0000u, 40u}, // kbp -> Latn
-    {0xC02A0000u, 40u}, // kbq -> Latn
-    {0xDC2A0000u, 40u}, // kbx -> Latn
+    {0x687A0000u, 44u}, // hz -> Latn
+    {0x69610000u, 44u}, // ia -> Latn
+    {0xB4080000u, 44u}, // ian -> Latn
+    {0xC4080000u, 44u}, // iar -> Latn
+    {0x80280000u, 44u}, // iba -> Latn
+    {0x84280000u, 44u}, // ibb -> Latn
+    {0xE0280000u, 44u}, // iby -> Latn
+    {0x80480000u, 44u}, // ica -> Latn
+    {0x9C480000u, 44u}, // ich -> Latn
+    {0x69640000u, 44u}, // id -> Latn
+    {0x8C680000u, 44u}, // idd -> Latn
+    {0xA0680000u, 44u}, // idi -> Latn
+    {0xD0680000u, 44u}, // idu -> Latn
+    {0x90A80000u, 44u}, // ife -> Latn
+    {0x69670000u, 44u}, // ig -> Latn
+    {0x84C80000u, 44u}, // igb -> Latn
+    {0x90C80000u, 44u}, // ige -> Latn
+    {0x69690000u, 94u}, // ii -> Yiii
+    {0xA5280000u, 44u}, // ijj -> Latn
+    {0x696B0000u, 44u}, // ik -> Latn
+    {0xA9480000u, 44u}, // ikk -> Latn
+    {0xCD480000u, 44u}, // ikt -> Latn
+    {0xD9480000u, 44u}, // ikw -> Latn
+    {0xDD480000u, 44u}, // ikx -> Latn
+    {0xB9680000u, 44u}, // ilo -> Latn
+    {0xB9880000u, 44u}, // imo -> Latn
+    {0x696E0000u, 44u}, // in -> Latn
+    {0x9DA80000u, 16u}, // inh -> Cyrl
+    {0x696F0000u, 44u}, // io -> Latn
+    {0xD1C80000u, 44u}, // iou -> Latn
+    {0xA2280000u, 44u}, // iri -> Latn
+    {0x69730000u, 44u}, // is -> Latn
+    {0x69740000u, 44u}, // it -> Latn
+    {0x69750000u, 10u}, // iu -> Cans
+    {0x69770000u, 30u}, // iw -> Hebr
+    {0xB2C80000u, 44u}, // iwm -> Latn
+    {0xCAC80000u, 44u}, // iws -> Latn
+    {0x9F280000u, 44u}, // izh -> Latn
+    {0xA3280000u, 44u}, // izi -> Latn
+    {0x6A610000u, 35u}, // ja -> Jpan
+    {0x84090000u, 44u}, // jab -> Latn
+    {0xB0090000u, 44u}, // jam -> Latn
+    {0xB8290000u, 44u}, // jbo -> Latn
+    {0xD0290000u, 44u}, // jbu -> Latn
+    {0xB4890000u, 44u}, // jen -> Latn
+    {0xA8C90000u, 44u}, // jgk -> Latn
+    {0xB8C90000u, 44u}, // jgo -> Latn
+    {0x6A690000u, 30u}, // ji -> Hebr
+    {0x85090000u, 44u}, // jib -> Latn
+    {0x89890000u, 44u}, // jmc -> Latn
+    {0xAD890000u, 17u}, // jml -> Deva
+    {0x82290000u, 44u}, // jra -> Latn
+    {0xCE890000u, 44u}, // jut -> Latn
+    {0x6A760000u, 44u}, // jv -> Latn
+    {0x6A770000u, 44u}, // jw -> Latn
+    {0x6B610000u, 20u}, // ka -> Geor
+    {0x800A0000u, 16u}, // kaa -> Cyrl
+    {0x840A0000u, 44u}, // kab -> Latn
+    {0x880A0000u, 44u}, // kac -> Latn
+    {0x8C0A0000u, 44u}, // kad -> Latn
+    {0xA00A0000u, 44u}, // kai -> Latn
+    {0xA40A0000u, 44u}, // kaj -> Latn
+    {0xB00A0000u, 44u}, // kam -> Latn
+    {0xB80A0000u, 44u}, // kao -> Latn
+    {0x8C2A0000u, 16u}, // kbd -> Cyrl
+    {0xB02A0000u, 44u}, // kbm -> Latn
+    {0xBC2A0000u, 44u}, // kbp -> Latn
+    {0xC02A0000u, 44u}, // kbq -> Latn
+    {0xDC2A0000u, 44u}, // kbx -> Latn
     {0xE02A0000u,  1u}, // kby -> Arab
-    {0x984A0000u, 40u}, // kcg -> Latn
-    {0xA84A0000u, 40u}, // kck -> Latn
-    {0xAC4A0000u, 40u}, // kcl -> Latn
-    {0xCC4A0000u, 40u}, // kct -> Latn
-    {0x906A0000u, 40u}, // kde -> Latn
+    {0x984A0000u, 44u}, // kcg -> Latn
+    {0xA84A0000u, 44u}, // kck -> Latn
+    {0xAC4A0000u, 44u}, // kcl -> Latn
+    {0xCC4A0000u, 44u}, // kct -> Latn
+    {0x906A0000u, 44u}, // kde -> Latn
     {0x9C6A0000u,  1u}, // kdh -> Arab
-    {0xAC6A0000u, 40u}, // kdl -> Latn
-    {0xCC6A0000u, 80u}, // kdt -> Thai
-    {0x808A0000u, 40u}, // kea -> Latn
-    {0xB48A0000u, 40u}, // ken -> Latn
-    {0xE48A0000u, 40u}, // kez -> Latn
-    {0xB8AA0000u, 40u}, // kfo -> Latn
-    {0xC4AA0000u, 16u}, // kfr -> Deva
-    {0xE0AA0000u, 16u}, // kfy -> Deva
-    {0x6B670000u, 40u}, // kg -> Latn
-    {0x90CA0000u, 40u}, // kge -> Latn
-    {0x94CA0000u, 40u}, // kgf -> Latn
-    {0xBCCA0000u, 40u}, // kgp -> Latn
-    {0x80EA0000u, 40u}, // kha -> Latn
-    {0x84EA0000u, 73u}, // khb -> Talu
-    {0xB4EA0000u, 16u}, // khn -> Deva
-    {0xC0EA0000u, 40u}, // khq -> Latn
-    {0xC8EA0000u, 40u}, // khs -> Latn
-    {0xCCEA0000u, 52u}, // kht -> Mymr
+    {0xAC6A0000u, 44u}, // kdl -> Latn
+    {0xCC6A0000u, 87u}, // kdt -> Thai
+    {0x808A0000u, 44u}, // kea -> Latn
+    {0xB48A0000u, 44u}, // ken -> Latn
+    {0xE48A0000u, 44u}, // kez -> Latn
+    {0xB8AA0000u, 44u}, // kfo -> Latn
+    {0xC4AA0000u, 17u}, // kfr -> Deva
+    {0xE0AA0000u, 17u}, // kfy -> Deva
+    {0x6B670000u, 44u}, // kg -> Latn
+    {0x90CA0000u, 44u}, // kge -> Latn
+    {0x94CA0000u, 44u}, // kgf -> Latn
+    {0xBCCA0000u, 44u}, // kgp -> Latn
+    {0x80EA0000u, 44u}, // kha -> Latn
+    {0x84EA0000u, 80u}, // khb -> Talu
+    {0xB4EA0000u, 17u}, // khn -> Deva
+    {0xC0EA0000u, 44u}, // khq -> Latn
+    {0xC8EA0000u, 44u}, // khs -> Latn
+    {0xCCEA0000u, 56u}, // kht -> Mymr
     {0xD8EA0000u,  1u}, // khw -> Arab
-    {0xE4EA0000u, 40u}, // khz -> Latn
-    {0x6B690000u, 40u}, // ki -> Latn
-    {0xA50A0000u, 40u}, // kij -> Latn
-    {0xD10A0000u, 40u}, // kiu -> Latn
-    {0xD90A0000u, 40u}, // kiw -> Latn
-    {0x6B6A0000u, 40u}, // kj -> Latn
-    {0x8D2A0000u, 40u}, // kjd -> Latn
-    {0x992A0000u, 39u}, // kjg -> Laoo
-    {0xC92A0000u, 40u}, // kjs -> Latn
-    {0xE12A0000u, 40u}, // kjy -> Latn
-    {0x6B6B0000u, 15u}, // kk -> Cyrl
+    {0xE4EA0000u, 44u}, // khz -> Latn
+    {0x6B690000u, 44u}, // ki -> Latn
+    {0xA50A0000u, 44u}, // kij -> Latn
+    {0xD10A0000u, 44u}, // kiu -> Latn
+    {0xD90A0000u, 44u}, // kiw -> Latn
+    {0x6B6A0000u, 44u}, // kj -> Latn
+    {0x8D2A0000u, 44u}, // kjd -> Latn
+    {0x992A0000u, 43u}, // kjg -> Laoo
+    {0xC92A0000u, 44u}, // kjs -> Latn
+    {0xE12A0000u, 44u}, // kjy -> Latn
+    {0x6B6B0000u, 16u}, // kk -> Cyrl
     {0x6B6B4146u,  1u}, // kk-AF -> Arab
     {0x6B6B434Eu,  1u}, // kk-CN -> Arab
     {0x6B6B4952u,  1u}, // kk-IR -> Arab
     {0x6B6B4D4Eu,  1u}, // kk-MN -> Arab
-    {0x894A0000u, 40u}, // kkc -> Latn
-    {0xA54A0000u, 40u}, // kkj -> Latn
-    {0x6B6C0000u, 40u}, // kl -> Latn
-    {0xB56A0000u, 40u}, // kln -> Latn
-    {0xC16A0000u, 40u}, // klq -> Latn
-    {0xCD6A0000u, 40u}, // klt -> Latn
-    {0xDD6A0000u, 40u}, // klx -> Latn
-    {0x6B6D0000u, 35u}, // km -> Khmr
-    {0x858A0000u, 40u}, // kmb -> Latn
-    {0x9D8A0000u, 40u}, // kmh -> Latn
-    {0xB98A0000u, 40u}, // kmo -> Latn
-    {0xC98A0000u, 40u}, // kms -> Latn
-    {0xD18A0000u, 40u}, // kmu -> Latn
-    {0xD98A0000u, 40u}, // kmw -> Latn
-    {0x6B6E0000u, 36u}, // kn -> Knda
-    {0xBDAA0000u, 40u}, // knp -> Latn
-    {0x6B6F0000u, 37u}, // ko -> Kore
-    {0xA1CA0000u, 15u}, // koi -> Cyrl
-    {0xA9CA0000u, 16u}, // kok -> Deva
-    {0xADCA0000u, 40u}, // kol -> Latn
-    {0xC9CA0000u, 40u}, // kos -> Latn
-    {0xE5CA0000u, 40u}, // koz -> Latn
-    {0x91EA0000u, 40u}, // kpe -> Latn
-    {0x95EA0000u, 40u}, // kpf -> Latn
-    {0xB9EA0000u, 40u}, // kpo -> Latn
-    {0xC5EA0000u, 40u}, // kpr -> Latn
-    {0xDDEA0000u, 40u}, // kpx -> Latn
-    {0x860A0000u, 40u}, // kqb -> Latn
-    {0x960A0000u, 40u}, // kqf -> Latn
-    {0xCA0A0000u, 40u}, // kqs -> Latn
-    {0xE20A0000u, 18u}, // kqy -> Ethi
-    {0x8A2A0000u, 15u}, // krc -> Cyrl
-    {0xA22A0000u, 40u}, // kri -> Latn
-    {0xA62A0000u, 40u}, // krj -> Latn
-    {0xAE2A0000u, 40u}, // krl -> Latn
-    {0xCA2A0000u, 40u}, // krs -> Latn
-    {0xD22A0000u, 16u}, // kru -> Deva
+    {0x894A0000u, 44u}, // kkc -> Latn
+    {0xA54A0000u, 44u}, // kkj -> Latn
+    {0x6B6C0000u, 44u}, // kl -> Latn
+    {0xB56A0000u, 44u}, // kln -> Latn
+    {0xC16A0000u, 44u}, // klq -> Latn
+    {0xCD6A0000u, 44u}, // klt -> Latn
+    {0xDD6A0000u, 44u}, // klx -> Latn
+    {0x6B6D0000u, 39u}, // km -> Khmr
+    {0x858A0000u, 44u}, // kmb -> Latn
+    {0x9D8A0000u, 44u}, // kmh -> Latn
+    {0xB98A0000u, 44u}, // kmo -> Latn
+    {0xC98A0000u, 44u}, // kms -> Latn
+    {0xD18A0000u, 44u}, // kmu -> Latn
+    {0xD98A0000u, 44u}, // kmw -> Latn
+    {0x6B6E0000u, 40u}, // kn -> Knda
+    {0x95AA0000u, 44u}, // knf -> Latn
+    {0xBDAA0000u, 44u}, // knp -> Latn
+    {0x6B6F0000u, 41u}, // ko -> Kore
+    {0xA1CA0000u, 16u}, // koi -> Cyrl
+    {0xA9CA0000u, 17u}, // kok -> Deva
+    {0xADCA0000u, 44u}, // kol -> Latn
+    {0xC9CA0000u, 44u}, // kos -> Latn
+    {0xE5CA0000u, 44u}, // koz -> Latn
+    {0x91EA0000u, 44u}, // kpe -> Latn
+    {0x95EA0000u, 44u}, // kpf -> Latn
+    {0xB9EA0000u, 44u}, // kpo -> Latn
+    {0xC5EA0000u, 44u}, // kpr -> Latn
+    {0xDDEA0000u, 44u}, // kpx -> Latn
+    {0x860A0000u, 44u}, // kqb -> Latn
+    {0x960A0000u, 44u}, // kqf -> Latn
+    {0xCA0A0000u, 44u}, // kqs -> Latn
+    {0xE20A0000u, 19u}, // kqy -> Ethi
+    {0x6B720000u, 44u}, // kr -> Latn
+    {0x8A2A0000u, 16u}, // krc -> Cyrl
+    {0xA22A0000u, 44u}, // kri -> Latn
+    {0xA62A0000u, 44u}, // krj -> Latn
+    {0xAE2A0000u, 44u}, // krl -> Latn
+    {0xCA2A0000u, 44u}, // krs -> Latn
+    {0xD22A0000u, 17u}, // kru -> Deva
     {0x6B730000u,  1u}, // ks -> Arab
-    {0x864A0000u, 40u}, // ksb -> Latn
-    {0x8E4A0000u, 40u}, // ksd -> Latn
-    {0x964A0000u, 40u}, // ksf -> Latn
-    {0x9E4A0000u, 40u}, // ksh -> Latn
-    {0xA64A0000u, 40u}, // ksj -> Latn
-    {0xC64A0000u, 40u}, // ksr -> Latn
-    {0x866A0000u, 18u}, // ktb -> Ethi
-    {0xB26A0000u, 40u}, // ktm -> Latn
-    {0xBA6A0000u, 40u}, // kto -> Latn
-    {0x6B750000u, 40u}, // ku -> Latn
+    {0x864A0000u, 44u}, // ksb -> Latn
+    {0x8E4A0000u, 44u}, // ksd -> Latn
+    {0x964A0000u, 44u}, // ksf -> Latn
+    {0x9E4A0000u, 44u}, // ksh -> Latn
+    {0xA64A0000u, 44u}, // ksj -> Latn
+    {0xC64A0000u, 44u}, // ksr -> Latn
+    {0x866A0000u, 19u}, // ktb -> Ethi
+    {0xB26A0000u, 44u}, // ktm -> Latn
+    {0xBA6A0000u, 44u}, // kto -> Latn
+    {0x6B750000u, 44u}, // ku -> Latn
     {0x6B754952u,  1u}, // ku-IR -> Arab
     {0x6B754C42u,  1u}, // ku-LB -> Arab
-    {0x868A0000u, 40u}, // kub -> Latn
-    {0x8E8A0000u, 40u}, // kud -> Latn
-    {0x928A0000u, 40u}, // kue -> Latn
-    {0xA68A0000u, 40u}, // kuj -> Latn
-    {0xB28A0000u, 15u}, // kum -> Cyrl
-    {0xB68A0000u, 40u}, // kun -> Latn
-    {0xBE8A0000u, 40u}, // kup -> Latn
-    {0xCA8A0000u, 40u}, // kus -> Latn
-    {0x6B760000u, 15u}, // kv -> Cyrl
-    {0x9AAA0000u, 40u}, // kvg -> Latn
-    {0xC6AA0000u, 40u}, // kvr -> Latn
+    {0x868A0000u, 44u}, // kub -> Latn
+    {0x8E8A0000u, 44u}, // kud -> Latn
+    {0x928A0000u, 44u}, // kue -> Latn
+    {0xA68A0000u, 44u}, // kuj -> Latn
+    {0xB28A0000u, 16u}, // kum -> Cyrl
+    {0xB68A0000u, 44u}, // kun -> Latn
+    {0xBE8A0000u, 44u}, // kup -> Latn
+    {0xCA8A0000u, 44u}, // kus -> Latn
+    {0x6B760000u, 16u}, // kv -> Cyrl
+    {0x9AAA0000u, 44u}, // kvg -> Latn
+    {0xC6AA0000u, 44u}, // kvr -> Latn
     {0xDEAA0000u,  1u}, // kvx -> Arab
-    {0x6B770000u, 40u}, // kw -> Latn
-    {0xA6CA0000u, 40u}, // kwj -> Latn
-    {0xBACA0000u, 40u}, // kwo -> Latn
-    {0x82EA0000u, 40u}, // kxa -> Latn
-    {0x8AEA0000u, 18u}, // kxc -> Ethi
-    {0xB2EA0000u, 80u}, // kxm -> Thai
+    {0x6B770000u, 44u}, // kw -> Latn
+    {0xA6CA0000u, 44u}, // kwj -> Latn
+    {0xBACA0000u, 44u}, // kwo -> Latn
+    {0x82EA0000u, 44u}, // kxa -> Latn
+    {0x8AEA0000u, 19u}, // kxc -> Ethi
+    {0xB2EA0000u, 87u}, // kxm -> Thai
     {0xBEEA0000u,  1u}, // kxp -> Arab
-    {0xDAEA0000u, 40u}, // kxw -> Latn
-    {0xE6EA0000u, 40u}, // kxz -> Latn
-    {0x6B790000u, 15u}, // ky -> Cyrl
+    {0xDAEA0000u, 44u}, // kxw -> Latn
+    {0xE6EA0000u, 44u}, // kxz -> Latn
+    {0x6B790000u, 16u}, // ky -> Cyrl
     {0x6B79434Eu,  1u}, // ky-CN -> Arab
-    {0x6B795452u, 40u}, // ky-TR -> Latn
-    {0x930A0000u, 40u}, // kye -> Latn
-    {0xDF0A0000u, 40u}, // kyx -> Latn
-    {0xC72A0000u, 40u}, // kzr -> Latn
-    {0x6C610000u, 40u}, // la -> Latn
-    {0x840B0000u, 42u}, // lab -> Lina
-    {0x8C0B0000u, 27u}, // lad -> Hebr
-    {0x980B0000u, 40u}, // lag -> Latn
+    {0x6B795452u, 44u}, // ky-TR -> Latn
+    {0x930A0000u, 44u}, // kye -> Latn
+    {0xDF0A0000u, 44u}, // kyx -> Latn
+    {0xC72A0000u, 44u}, // kzr -> Latn
+    {0x6C610000u, 44u}, // la -> Latn
+    {0x840B0000u, 46u}, // lab -> Lina
+    {0x8C0B0000u, 30u}, // lad -> Hebr
+    {0x980B0000u, 44u}, // lag -> Latn
     {0x9C0B0000u,  1u}, // lah -> Arab
-    {0xA40B0000u, 40u}, // laj -> Latn
-    {0xC80B0000u, 40u}, // las -> Latn
-    {0x6C620000u, 40u}, // lb -> Latn
-    {0x902B0000u, 15u}, // lbe -> Cyrl
-    {0xD02B0000u, 40u}, // lbu -> Latn
-    {0xD82B0000u, 40u}, // lbw -> Latn
-    {0xB04B0000u, 40u}, // lcm -> Latn
-    {0xBC4B0000u, 80u}, // lcp -> Thai
-    {0x846B0000u, 40u}, // ldb -> Latn
-    {0x8C8B0000u, 40u}, // led -> Latn
-    {0x908B0000u, 40u}, // lee -> Latn
-    {0xB08B0000u, 40u}, // lem -> Latn
-    {0xBC8B0000u, 41u}, // lep -> Lepc
-    {0xC08B0000u, 40u}, // leq -> Latn
-    {0xD08B0000u, 40u}, // leu -> Latn
-    {0xE48B0000u, 15u}, // lez -> Cyrl
-    {0x6C670000u, 40u}, // lg -> Latn
-    {0x98CB0000u, 40u}, // lgg -> Latn
-    {0x6C690000u, 40u}, // li -> Latn
-    {0x810B0000u, 40u}, // lia -> Latn
-    {0x8D0B0000u, 40u}, // lid -> Latn
-    {0x950B0000u, 16u}, // lif -> Deva
-    {0x990B0000u, 40u}, // lig -> Latn
-    {0x9D0B0000u, 40u}, // lih -> Latn
-    {0xA50B0000u, 40u}, // lij -> Latn
-    {0xC90B0000u, 43u}, // lis -> Lisu
-    {0xBD2B0000u, 40u}, // ljp -> Latn
+    {0xA40B0000u, 44u}, // laj -> Latn
+    {0xC80B0000u, 44u}, // las -> Latn
+    {0x6C620000u, 44u}, // lb -> Latn
+    {0x902B0000u, 16u}, // lbe -> Cyrl
+    {0xD02B0000u, 44u}, // lbu -> Latn
+    {0xD82B0000u, 44u}, // lbw -> Latn
+    {0xB04B0000u, 44u}, // lcm -> Latn
+    {0xBC4B0000u, 87u}, // lcp -> Thai
+    {0x846B0000u, 44u}, // ldb -> Latn
+    {0x8C8B0000u, 44u}, // led -> Latn
+    {0x908B0000u, 44u}, // lee -> Latn
+    {0xB08B0000u, 44u}, // lem -> Latn
+    {0xBC8B0000u, 45u}, // lep -> Lepc
+    {0xC08B0000u, 44u}, // leq -> Latn
+    {0xD08B0000u, 44u}, // leu -> Latn
+    {0xE48B0000u, 16u}, // lez -> Cyrl
+    {0x6C670000u, 44u}, // lg -> Latn
+    {0x98CB0000u, 44u}, // lgg -> Latn
+    {0x6C690000u, 44u}, // li -> Latn
+    {0x810B0000u, 44u}, // lia -> Latn
+    {0x8D0B0000u, 44u}, // lid -> Latn
+    {0x950B0000u, 17u}, // lif -> Deva
+    {0x990B0000u, 44u}, // lig -> Latn
+    {0x9D0B0000u, 44u}, // lih -> Latn
+    {0xA50B0000u, 44u}, // lij -> Latn
+    {0xC90B0000u, 47u}, // lis -> Lisu
+    {0xBD2B0000u, 44u}, // ljp -> Latn
     {0xA14B0000u,  1u}, // lki -> Arab
-    {0xCD4B0000u, 40u}, // lkt -> Latn
-    {0x916B0000u, 40u}, // lle -> Latn
-    {0xB56B0000u, 40u}, // lln -> Latn
-    {0xB58B0000u, 77u}, // lmn -> Telu
-    {0xB98B0000u, 40u}, // lmo -> Latn
-    {0xBD8B0000u, 40u}, // lmp -> Latn
-    {0x6C6E0000u, 40u}, // ln -> Latn
-    {0xC9AB0000u, 40u}, // lns -> Latn
-    {0xD1AB0000u, 40u}, // lnu -> Latn
-    {0x6C6F0000u, 39u}, // lo -> Laoo
-    {0xA5CB0000u, 40u}, // loj -> Latn
-    {0xA9CB0000u, 40u}, // lok -> Latn
-    {0xADCB0000u, 40u}, // lol -> Latn
-    {0xC5CB0000u, 40u}, // lor -> Latn
-    {0xC9CB0000u, 40u}, // los -> Latn
-    {0xE5CB0000u, 40u}, // loz -> Latn
+    {0xCD4B0000u, 44u}, // lkt -> Latn
+    {0x916B0000u, 44u}, // lle -> Latn
+    {0xB56B0000u, 44u}, // lln -> Latn
+    {0xB58B0000u, 84u}, // lmn -> Telu
+    {0xB98B0000u, 44u}, // lmo -> Latn
+    {0xBD8B0000u, 44u}, // lmp -> Latn
+    {0x6C6E0000u, 44u}, // ln -> Latn
+    {0xC9AB0000u, 44u}, // lns -> Latn
+    {0xD1AB0000u, 44u}, // lnu -> Latn
+    {0x6C6F0000u, 43u}, // lo -> Laoo
+    {0xA5CB0000u, 44u}, // loj -> Latn
+    {0xA9CB0000u, 44u}, // lok -> Latn
+    {0xADCB0000u, 44u}, // lol -> Latn
+    {0xC5CB0000u, 44u}, // lor -> Latn
+    {0xC9CB0000u, 44u}, // los -> Latn
+    {0xE5CB0000u, 44u}, // loz -> Latn
     {0x8A2B0000u,  1u}, // lrc -> Arab
-    {0x6C740000u, 40u}, // lt -> Latn
-    {0x9A6B0000u, 40u}, // ltg -> Latn
-    {0x6C750000u, 40u}, // lu -> Latn
-    {0x828B0000u, 40u}, // lua -> Latn
-    {0xBA8B0000u, 40u}, // luo -> Latn
-    {0xE28B0000u, 40u}, // luy -> Latn
+    {0x6C740000u, 44u}, // lt -> Latn
+    {0x9A6B0000u, 44u}, // ltg -> Latn
+    {0x6C750000u, 44u}, // lu -> Latn
+    {0x828B0000u, 44u}, // lua -> Latn
+    {0xBA8B0000u, 44u}, // luo -> Latn
+    {0xE28B0000u, 44u}, // luy -> Latn
     {0xE68B0000u,  1u}, // luz -> Arab
-    {0x6C760000u, 40u}, // lv -> Latn
-    {0xAECB0000u, 80u}, // lwl -> Thai
-    {0x9F2B0000u, 24u}, // lzh -> Hans
-    {0xE72B0000u, 40u}, // lzz -> Latn
-    {0x8C0C0000u, 40u}, // mad -> Latn
-    {0x940C0000u, 40u}, // maf -> Latn
-    {0x980C0000u, 16u}, // mag -> Deva
-    {0xA00C0000u, 16u}, // mai -> Deva
-    {0xA80C0000u, 40u}, // mak -> Latn
-    {0xB40C0000u, 40u}, // man -> Latn
-    {0xB40C474Eu, 54u}, // man-GN -> Nkoo
-    {0xC80C0000u, 40u}, // mas -> Latn
-    {0xD80C0000u, 40u}, // maw -> Latn
-    {0xE40C0000u, 40u}, // maz -> Latn
-    {0x9C2C0000u, 40u}, // mbh -> Latn
-    {0xB82C0000u, 40u}, // mbo -> Latn
-    {0xC02C0000u, 40u}, // mbq -> Latn
-    {0xD02C0000u, 40u}, // mbu -> Latn
-    {0xD82C0000u, 40u}, // mbw -> Latn
-    {0xA04C0000u, 40u}, // mci -> Latn
-    {0xBC4C0000u, 40u}, // mcp -> Latn
-    {0xC04C0000u, 40u}, // mcq -> Latn
-    {0xC44C0000u, 40u}, // mcr -> Latn
-    {0xD04C0000u, 40u}, // mcu -> Latn
-    {0x806C0000u, 40u}, // mda -> Latn
+    {0x6C760000u, 44u}, // lv -> Latn
+    {0xAECB0000u, 87u}, // lwl -> Thai
+    {0x9F2B0000u, 27u}, // lzh -> Hans
+    {0xE72B0000u, 44u}, // lzz -> Latn
+    {0x8C0C0000u, 44u}, // mad -> Latn
+    {0x940C0000u, 44u}, // maf -> Latn
+    {0x980C0000u, 17u}, // mag -> Deva
+    {0xA00C0000u, 17u}, // mai -> Deva
+    {0xA80C0000u, 44u}, // mak -> Latn
+    {0xB40C0000u, 44u}, // man -> Latn
+    {0xB40C474Eu, 58u}, // man-GN -> Nkoo
+    {0xC80C0000u, 44u}, // mas -> Latn
+    {0xD80C0000u, 44u}, // maw -> Latn
+    {0xE40C0000u, 44u}, // maz -> Latn
+    {0x9C2C0000u, 44u}, // mbh -> Latn
+    {0xB82C0000u, 44u}, // mbo -> Latn
+    {0xC02C0000u, 44u}, // mbq -> Latn
+    {0xD02C0000u, 44u}, // mbu -> Latn
+    {0xD82C0000u, 44u}, // mbw -> Latn
+    {0xA04C0000u, 44u}, // mci -> Latn
+    {0xBC4C0000u, 44u}, // mcp -> Latn
+    {0xC04C0000u, 44u}, // mcq -> Latn
+    {0xC44C0000u, 44u}, // mcr -> Latn
+    {0xD04C0000u, 44u}, // mcu -> Latn
+    {0x806C0000u, 44u}, // mda -> Latn
     {0x906C0000u,  1u}, // mde -> Arab
-    {0x946C0000u, 15u}, // mdf -> Cyrl
-    {0x9C6C0000u, 40u}, // mdh -> Latn
-    {0xA46C0000u, 40u}, // mdj -> Latn
-    {0xC46C0000u, 40u}, // mdr -> Latn
-    {0xDC6C0000u, 18u}, // mdx -> Ethi
-    {0x8C8C0000u, 40u}, // med -> Latn
-    {0x908C0000u, 40u}, // mee -> Latn
-    {0xA88C0000u, 40u}, // mek -> Latn
-    {0xB48C0000u, 40u}, // men -> Latn
-    {0xC48C0000u, 40u}, // mer -> Latn
-    {0xCC8C0000u, 40u}, // met -> Latn
-    {0xD08C0000u, 40u}, // meu -> Latn
+    {0x946C0000u, 16u}, // mdf -> Cyrl
+    {0x9C6C0000u, 44u}, // mdh -> Latn
+    {0xA46C0000u, 44u}, // mdj -> Latn
+    {0xC46C0000u, 44u}, // mdr -> Latn
+    {0xDC6C0000u, 19u}, // mdx -> Ethi
+    {0x8C8C0000u, 44u}, // med -> Latn
+    {0x908C0000u, 44u}, // mee -> Latn
+    {0xA88C0000u, 44u}, // mek -> Latn
+    {0xB48C0000u, 44u}, // men -> Latn
+    {0xC48C0000u, 44u}, // mer -> Latn
+    {0xCC8C0000u, 44u}, // met -> Latn
+    {0xD08C0000u, 44u}, // meu -> Latn
     {0x80AC0000u,  1u}, // mfa -> Arab
-    {0x90AC0000u, 40u}, // mfe -> Latn
-    {0xB4AC0000u, 40u}, // mfn -> Latn
-    {0xB8AC0000u, 40u}, // mfo -> Latn
-    {0xC0AC0000u, 40u}, // mfq -> Latn
-    {0x6D670000u, 40u}, // mg -> Latn
-    {0x9CCC0000u, 40u}, // mgh -> Latn
-    {0xACCC0000u, 40u}, // mgl -> Latn
-    {0xB8CC0000u, 40u}, // mgo -> Latn
-    {0xBCCC0000u, 16u}, // mgp -> Deva
-    {0xE0CC0000u, 40u}, // mgy -> Latn
-    {0x6D680000u, 40u}, // mh -> Latn
-    {0xA0EC0000u, 40u}, // mhi -> Latn
-    {0xACEC0000u, 40u}, // mhl -> Latn
-    {0x6D690000u, 40u}, // mi -> Latn
-    {0x950C0000u, 40u}, // mif -> Latn
-    {0xB50C0000u, 40u}, // min -> Latn
-    {0xC90C0000u, 26u}, // mis -> Hatr
-    {0xD90C0000u, 40u}, // miw -> Latn
-    {0x6D6B0000u, 15u}, // mk -> Cyrl
+    {0x90AC0000u, 44u}, // mfe -> Latn
+    {0xB4AC0000u, 44u}, // mfn -> Latn
+    {0xB8AC0000u, 44u}, // mfo -> Latn
+    {0xC0AC0000u, 44u}, // mfq -> Latn
+    {0x6D670000u, 44u}, // mg -> Latn
+    {0x9CCC0000u, 44u}, // mgh -> Latn
+    {0xACCC0000u, 44u}, // mgl -> Latn
+    {0xB8CC0000u, 44u}, // mgo -> Latn
+    {0xBCCC0000u, 17u}, // mgp -> Deva
+    {0xE0CC0000u, 44u}, // mgy -> Latn
+    {0x6D680000u, 44u}, // mh -> Latn
+    {0xA0EC0000u, 44u}, // mhi -> Latn
+    {0xACEC0000u, 44u}, // mhl -> Latn
+    {0x6D690000u, 44u}, // mi -> Latn
+    {0x950C0000u, 44u}, // mif -> Latn
+    {0xB50C0000u, 44u}, // min -> Latn
+    {0xC90C0000u, 29u}, // mis -> Hatr
+    {0xD90C0000u, 44u}, // miw -> Latn
+    {0x6D6B0000u, 16u}, // mk -> Cyrl
     {0xA14C0000u,  1u}, // mki -> Arab
-    {0xAD4C0000u, 40u}, // mkl -> Latn
-    {0xBD4C0000u, 40u}, // mkp -> Latn
-    {0xD94C0000u, 40u}, // mkw -> Latn
-    {0x6D6C0000u, 49u}, // ml -> Mlym
-    {0x916C0000u, 40u}, // mle -> Latn
-    {0xBD6C0000u, 40u}, // mlp -> Latn
-    {0xC96C0000u, 40u}, // mls -> Latn
-    {0xB98C0000u, 40u}, // mmo -> Latn
-    {0xD18C0000u, 40u}, // mmu -> Latn
-    {0xDD8C0000u, 40u}, // mmx -> Latn
-    {0x6D6E0000u, 15u}, // mn -> Cyrl
-    {0x6D6E434Eu, 50u}, // mn-CN -> Mong
-    {0x81AC0000u, 40u}, // mna -> Latn
-    {0x95AC0000u, 40u}, // mnf -> Latn
+    {0xAD4C0000u, 44u}, // mkl -> Latn
+    {0xBD4C0000u, 44u}, // mkp -> Latn
+    {0xD94C0000u, 44u}, // mkw -> Latn
+    {0x6D6C0000u, 53u}, // ml -> Mlym
+    {0x916C0000u, 44u}, // mle -> Latn
+    {0xBD6C0000u, 44u}, // mlp -> Latn
+    {0xC96C0000u, 44u}, // mls -> Latn
+    {0xB98C0000u, 44u}, // mmo -> Latn
+    {0xD18C0000u, 44u}, // mmu -> Latn
+    {0xDD8C0000u, 44u}, // mmx -> Latn
+    {0x6D6E0000u, 16u}, // mn -> Cyrl
+    {0x6D6E434Eu, 54u}, // mn-CN -> Mong
+    {0x81AC0000u, 44u}, // mna -> Latn
+    {0x95AC0000u, 44u}, // mnf -> Latn
     {0xA1AC0000u,  7u}, // mni -> Beng
-    {0xD9AC0000u, 52u}, // mnw -> Mymr
-    {0x81CC0000u, 40u}, // moa -> Latn
-    {0x91CC0000u, 40u}, // moe -> Latn
-    {0x9DCC0000u, 40u}, // moh -> Latn
-    {0xC9CC0000u, 40u}, // mos -> Latn
-    {0xDDCC0000u, 40u}, // mox -> Latn
-    {0xBDEC0000u, 40u}, // mpp -> Latn
-    {0xC9EC0000u, 40u}, // mps -> Latn
-    {0xCDEC0000u, 40u}, // mpt -> Latn
-    {0xDDEC0000u, 40u}, // mpx -> Latn
-    {0xAE0C0000u, 40u}, // mql -> Latn
-    {0x6D720000u, 16u}, // mr -> Deva
-    {0x8E2C0000u, 16u}, // mrd -> Deva
-    {0xA62C0000u, 15u}, // mrj -> Cyrl
-    {0xBA2C0000u, 51u}, // mro -> Mroo
-    {0x6D730000u, 40u}, // ms -> Latn
+    {0xD9AC0000u, 56u}, // mnw -> Mymr
+    {0x81CC0000u, 44u}, // moa -> Latn
+    {0x91CC0000u, 44u}, // moe -> Latn
+    {0x9DCC0000u, 44u}, // moh -> Latn
+    {0xC9CC0000u, 44u}, // mos -> Latn
+    {0xDDCC0000u, 44u}, // mox -> Latn
+    {0xBDEC0000u, 44u}, // mpp -> Latn
+    {0xC9EC0000u, 44u}, // mps -> Latn
+    {0xCDEC0000u, 44u}, // mpt -> Latn
+    {0xDDEC0000u, 44u}, // mpx -> Latn
+    {0xAE0C0000u, 44u}, // mql -> Latn
+    {0x6D720000u, 17u}, // mr -> Deva
+    {0x8E2C0000u, 17u}, // mrd -> Deva
+    {0xA62C0000u, 16u}, // mrj -> Cyrl
+    {0xBA2C0000u, 55u}, // mro -> Mroo
+    {0x6D730000u, 44u}, // ms -> Latn
     {0x6D734343u,  1u}, // ms-CC -> Arab
     {0x6D734944u,  1u}, // ms-ID -> Arab
-    {0x6D740000u, 40u}, // mt -> Latn
-    {0x8A6C0000u, 40u}, // mtc -> Latn
-    {0x966C0000u, 40u}, // mtf -> Latn
-    {0xA26C0000u, 40u}, // mti -> Latn
-    {0xC66C0000u, 16u}, // mtr -> Deva
-    {0x828C0000u, 40u}, // mua -> Latn
-    {0xC68C0000u, 40u}, // mur -> Latn
-    {0xCA8C0000u, 40u}, // mus -> Latn
-    {0x82AC0000u, 40u}, // mva -> Latn
-    {0xB6AC0000u, 40u}, // mvn -> Latn
+    {0x6D740000u, 44u}, // mt -> Latn
+    {0x8A6C0000u, 44u}, // mtc -> Latn
+    {0x966C0000u, 44u}, // mtf -> Latn
+    {0xA26C0000u, 44u}, // mti -> Latn
+    {0xC66C0000u, 17u}, // mtr -> Deva
+    {0x828C0000u, 44u}, // mua -> Latn
+    {0xC68C0000u, 44u}, // mur -> Latn
+    {0xCA8C0000u, 44u}, // mus -> Latn
+    {0x82AC0000u, 44u}, // mva -> Latn
+    {0xB6AC0000u, 44u}, // mvn -> Latn
     {0xE2AC0000u,  1u}, // mvy -> Arab
-    {0xAACC0000u, 40u}, // mwk -> Latn
-    {0xC6CC0000u, 16u}, // mwr -> Deva
-    {0xD6CC0000u, 40u}, // mwv -> Latn
-    {0x8AEC0000u, 40u}, // mxc -> Latn
-    {0xB2EC0000u, 40u}, // mxm -> Latn
-    {0x6D790000u, 52u}, // my -> Mymr
-    {0xAB0C0000u, 40u}, // myk -> Latn
-    {0xB30C0000u, 18u}, // mym -> Ethi
-    {0xD70C0000u, 15u}, // myv -> Cyrl
-    {0xDB0C0000u, 40u}, // myw -> Latn
-    {0xDF0C0000u, 40u}, // myx -> Latn
-    {0xE70C0000u, 46u}, // myz -> Mand
-    {0xAB2C0000u, 40u}, // mzk -> Latn
-    {0xB32C0000u, 40u}, // mzm -> Latn
+    {0xAACC0000u, 44u}, // mwk -> Latn
+    {0xC6CC0000u, 17u}, // mwr -> Deva
+    {0xD6CC0000u, 44u}, // mwv -> Latn
+    {0xDACC0000u, 33u}, // mww -> Hmnp
+    {0x8AEC0000u, 44u}, // mxc -> Latn
+    {0xB2EC0000u, 44u}, // mxm -> Latn
+    {0x6D790000u, 56u}, // my -> Mymr
+    {0xAB0C0000u, 44u}, // myk -> Latn
+    {0xB30C0000u, 19u}, // mym -> Ethi
+    {0xD70C0000u, 16u}, // myv -> Cyrl
+    {0xDB0C0000u, 44u}, // myw -> Latn
+    {0xDF0C0000u, 44u}, // myx -> Latn
+    {0xE70C0000u, 50u}, // myz -> Mand
+    {0xAB2C0000u, 44u}, // mzk -> Latn
+    {0xB32C0000u, 44u}, // mzm -> Latn
     {0xB72C0000u,  1u}, // mzn -> Arab
-    {0xBF2C0000u, 40u}, // mzp -> Latn
-    {0xDB2C0000u, 40u}, // mzw -> Latn
-    {0xE72C0000u, 40u}, // mzz -> Latn
-    {0x6E610000u, 40u}, // na -> Latn
-    {0x880D0000u, 40u}, // nac -> Latn
-    {0x940D0000u, 40u}, // naf -> Latn
-    {0xA80D0000u, 40u}, // nak -> Latn
-    {0xB40D0000u, 24u}, // nan -> Hans
-    {0xBC0D0000u, 40u}, // nap -> Latn
-    {0xC00D0000u, 40u}, // naq -> Latn
-    {0xC80D0000u, 40u}, // nas -> Latn
-    {0x6E620000u, 40u}, // nb -> Latn
-    {0x804D0000u, 40u}, // nca -> Latn
-    {0x904D0000u, 40u}, // nce -> Latn
-    {0x944D0000u, 40u}, // ncf -> Latn
-    {0x9C4D0000u, 40u}, // nch -> Latn
-    {0xB84D0000u, 40u}, // nco -> Latn
-    {0xD04D0000u, 40u}, // ncu -> Latn
-    {0x6E640000u, 40u}, // nd -> Latn
-    {0x886D0000u, 40u}, // ndc -> Latn
-    {0xC86D0000u, 40u}, // nds -> Latn
-    {0x6E650000u, 16u}, // ne -> Deva
-    {0x848D0000u, 40u}, // neb -> Latn
-    {0xD88D0000u, 16u}, // new -> Deva
-    {0xDC8D0000u, 40u}, // nex -> Latn
-    {0xC4AD0000u, 40u}, // nfr -> Latn
-    {0x6E670000u, 40u}, // ng -> Latn
-    {0x80CD0000u, 40u}, // nga -> Latn
-    {0x84CD0000u, 40u}, // ngb -> Latn
-    {0xACCD0000u, 40u}, // ngl -> Latn
-    {0x84ED0000u, 40u}, // nhb -> Latn
-    {0x90ED0000u, 40u}, // nhe -> Latn
-    {0xD8ED0000u, 40u}, // nhw -> Latn
-    {0x950D0000u, 40u}, // nif -> Latn
-    {0xA10D0000u, 40u}, // nii -> Latn
-    {0xA50D0000u, 40u}, // nij -> Latn
-    {0xB50D0000u, 40u}, // nin -> Latn
-    {0xD10D0000u, 40u}, // niu -> Latn
-    {0xE10D0000u, 40u}, // niy -> Latn
-    {0xE50D0000u, 40u}, // niz -> Latn
-    {0xB92D0000u, 40u}, // njo -> Latn
-    {0x994D0000u, 40u}, // nkg -> Latn
-    {0xB94D0000u, 40u}, // nko -> Latn
-    {0x6E6C0000u, 40u}, // nl -> Latn
-    {0x998D0000u, 40u}, // nmg -> Latn
-    {0xE58D0000u, 40u}, // nmz -> Latn
-    {0x6E6E0000u, 40u}, // nn -> Latn
-    {0x95AD0000u, 40u}, // nnf -> Latn
-    {0x9DAD0000u, 40u}, // nnh -> Latn
-    {0xA9AD0000u, 40u}, // nnk -> Latn
-    {0xB1AD0000u, 40u}, // nnm -> Latn
-    {0x6E6F0000u, 40u}, // no -> Latn
-    {0x8DCD0000u, 38u}, // nod -> Lana
-    {0x91CD0000u, 16u}, // noe -> Deva
-    {0xB5CD0000u, 64u}, // non -> Runr
-    {0xBDCD0000u, 40u}, // nop -> Latn
-    {0xD1CD0000u, 40u}, // nou -> Latn
-    {0xBA0D0000u, 54u}, // nqo -> Nkoo
-    {0x6E720000u, 40u}, // nr -> Latn
-    {0x862D0000u, 40u}, // nrb -> Latn
-    {0xAA4D0000u,  9u}, // nsk -> Cans
-    {0xB64D0000u, 40u}, // nsn -> Latn
-    {0xBA4D0000u, 40u}, // nso -> Latn
-    {0xCA4D0000u, 40u}, // nss -> Latn
-    {0xB26D0000u, 40u}, // ntm -> Latn
-    {0xC66D0000u, 40u}, // ntr -> Latn
-    {0xA28D0000u, 40u}, // nui -> Latn
-    {0xBE8D0000u, 40u}, // nup -> Latn
-    {0xCA8D0000u, 40u}, // nus -> Latn
-    {0xD68D0000u, 40u}, // nuv -> Latn
-    {0xDE8D0000u, 40u}, // nux -> Latn
-    {0x6E760000u, 40u}, // nv -> Latn
-    {0x86CD0000u, 40u}, // nwb -> Latn
-    {0xC2ED0000u, 40u}, // nxq -> Latn
-    {0xC6ED0000u, 40u}, // nxr -> Latn
-    {0x6E790000u, 40u}, // ny -> Latn
-    {0xB30D0000u, 40u}, // nym -> Latn
-    {0xB70D0000u, 40u}, // nyn -> Latn
-    {0xA32D0000u, 40u}, // nzi -> Latn
-    {0x6F630000u, 40u}, // oc -> Latn
-    {0x88CE0000u, 40u}, // ogc -> Latn
-    {0xC54E0000u, 40u}, // okr -> Latn
-    {0xD54E0000u, 40u}, // okv -> Latn
-    {0x6F6D0000u, 40u}, // om -> Latn
-    {0x99AE0000u, 40u}, // ong -> Latn
-    {0xB5AE0000u, 40u}, // onn -> Latn
-    {0xC9AE0000u, 40u}, // ons -> Latn
-    {0xB1EE0000u, 40u}, // opm -> Latn
-    {0x6F720000u, 57u}, // or -> Orya
-    {0xBA2E0000u, 40u}, // oro -> Latn
+    {0xBF2C0000u, 44u}, // mzp -> Latn
+    {0xDB2C0000u, 44u}, // mzw -> Latn
+    {0xE72C0000u, 44u}, // mzz -> Latn
+    {0x6E610000u, 44u}, // na -> Latn
+    {0x880D0000u, 44u}, // nac -> Latn
+    {0x940D0000u, 44u}, // naf -> Latn
+    {0xA80D0000u, 44u}, // nak -> Latn
+    {0xB40D0000u, 27u}, // nan -> Hans
+    {0xBC0D0000u, 44u}, // nap -> Latn
+    {0xC00D0000u, 44u}, // naq -> Latn
+    {0xC80D0000u, 44u}, // nas -> Latn
+    {0x6E620000u, 44u}, // nb -> Latn
+    {0x804D0000u, 44u}, // nca -> Latn
+    {0x904D0000u, 44u}, // nce -> Latn
+    {0x944D0000u, 44u}, // ncf -> Latn
+    {0x9C4D0000u, 44u}, // nch -> Latn
+    {0xB84D0000u, 44u}, // nco -> Latn
+    {0xD04D0000u, 44u}, // ncu -> Latn
+    {0x6E640000u, 44u}, // nd -> Latn
+    {0x886D0000u, 44u}, // ndc -> Latn
+    {0xC86D0000u, 44u}, // nds -> Latn
+    {0x6E650000u, 17u}, // ne -> Deva
+    {0x848D0000u, 44u}, // neb -> Latn
+    {0xD88D0000u, 17u}, // new -> Deva
+    {0xDC8D0000u, 44u}, // nex -> Latn
+    {0xC4AD0000u, 44u}, // nfr -> Latn
+    {0x6E670000u, 44u}, // ng -> Latn
+    {0x80CD0000u, 44u}, // nga -> Latn
+    {0x84CD0000u, 44u}, // ngb -> Latn
+    {0xACCD0000u, 44u}, // ngl -> Latn
+    {0x84ED0000u, 44u}, // nhb -> Latn
+    {0x90ED0000u, 44u}, // nhe -> Latn
+    {0xD8ED0000u, 44u}, // nhw -> Latn
+    {0x950D0000u, 44u}, // nif -> Latn
+    {0xA10D0000u, 44u}, // nii -> Latn
+    {0xA50D0000u, 44u}, // nij -> Latn
+    {0xB50D0000u, 44u}, // nin -> Latn
+    {0xD10D0000u, 44u}, // niu -> Latn
+    {0xE10D0000u, 44u}, // niy -> Latn
+    {0xE50D0000u, 44u}, // niz -> Latn
+    {0xB92D0000u, 44u}, // njo -> Latn
+    {0x994D0000u, 44u}, // nkg -> Latn
+    {0xB94D0000u, 44u}, // nko -> Latn
+    {0x6E6C0000u, 44u}, // nl -> Latn
+    {0x998D0000u, 44u}, // nmg -> Latn
+    {0xE58D0000u, 44u}, // nmz -> Latn
+    {0x6E6E0000u, 44u}, // nn -> Latn
+    {0x95AD0000u, 44u}, // nnf -> Latn
+    {0x9DAD0000u, 44u}, // nnh -> Latn
+    {0xA9AD0000u, 44u}, // nnk -> Latn
+    {0xB1AD0000u, 44u}, // nnm -> Latn
+    {0xBDAD0000u, 91u}, // nnp -> Wcho
+    {0x6E6F0000u, 44u}, // no -> Latn
+    {0x8DCD0000u, 42u}, // nod -> Lana
+    {0x91CD0000u, 17u}, // noe -> Deva
+    {0xB5CD0000u, 69u}, // non -> Runr
+    {0xBDCD0000u, 44u}, // nop -> Latn
+    {0xD1CD0000u, 44u}, // nou -> Latn
+    {0xBA0D0000u, 58u}, // nqo -> Nkoo
+    {0x6E720000u, 44u}, // nr -> Latn
+    {0x862D0000u, 44u}, // nrb -> Latn
+    {0xAA4D0000u, 10u}, // nsk -> Cans
+    {0xB64D0000u, 44u}, // nsn -> Latn
+    {0xBA4D0000u, 44u}, // nso -> Latn
+    {0xCA4D0000u, 44u}, // nss -> Latn
+    {0xB26D0000u, 44u}, // ntm -> Latn
+    {0xC66D0000u, 44u}, // ntr -> Latn
+    {0xA28D0000u, 44u}, // nui -> Latn
+    {0xBE8D0000u, 44u}, // nup -> Latn
+    {0xCA8D0000u, 44u}, // nus -> Latn
+    {0xD68D0000u, 44u}, // nuv -> Latn
+    {0xDE8D0000u, 44u}, // nux -> Latn
+    {0x6E760000u, 44u}, // nv -> Latn
+    {0x86CD0000u, 44u}, // nwb -> Latn
+    {0xC2ED0000u, 44u}, // nxq -> Latn
+    {0xC6ED0000u, 44u}, // nxr -> Latn
+    {0x6E790000u, 44u}, // ny -> Latn
+    {0xB30D0000u, 44u}, // nym -> Latn
+    {0xB70D0000u, 44u}, // nyn -> Latn
+    {0xA32D0000u, 44u}, // nzi -> Latn
+    {0x6F630000u, 44u}, // oc -> Latn
+    {0x88CE0000u, 44u}, // ogc -> Latn
+    {0xC54E0000u, 44u}, // okr -> Latn
+    {0xD54E0000u, 44u}, // okv -> Latn
+    {0x6F6D0000u, 44u}, // om -> Latn
+    {0x99AE0000u, 44u}, // ong -> Latn
+    {0xB5AE0000u, 44u}, // onn -> Latn
+    {0xC9AE0000u, 44u}, // ons -> Latn
+    {0xB1EE0000u, 44u}, // opm -> Latn
+    {0x6F720000u, 62u}, // or -> Orya
+    {0xBA2E0000u, 44u}, // oro -> Latn
     {0xD22E0000u,  1u}, // oru -> Arab
-    {0x6F730000u, 15u}, // os -> Cyrl
-    {0x824E0000u, 58u}, // osa -> Osge
+    {0x6F730000u, 16u}, // os -> Cyrl
+    {0x824E0000u, 63u}, // osa -> Osge
     {0x826E0000u,  1u}, // ota -> Arab
-    {0xAA6E0000u, 56u}, // otk -> Orkh
-    {0xB32E0000u, 40u}, // ozm -> Latn
-    {0x70610000u, 23u}, // pa -> Guru
+    {0xAA6E0000u, 61u}, // otk -> Orkh
+    {0xB32E0000u, 44u}, // ozm -> Latn
+    {0x70610000u, 26u}, // pa -> Guru
     {0x7061504Bu,  1u}, // pa-PK -> Arab
-    {0x980F0000u, 40u}, // pag -> Latn
-    {0xAC0F0000u, 60u}, // pal -> Phli
-    {0xB00F0000u, 40u}, // pam -> Latn
-    {0xBC0F0000u, 40u}, // pap -> Latn
-    {0xD00F0000u, 40u}, // pau -> Latn
-    {0xA02F0000u, 40u}, // pbi -> Latn
-    {0x8C4F0000u, 40u}, // pcd -> Latn
-    {0xB04F0000u, 40u}, // pcm -> Latn
-    {0x886F0000u, 40u}, // pdc -> Latn
-    {0xCC6F0000u, 40u}, // pdt -> Latn
-    {0x8C8F0000u, 40u}, // ped -> Latn
-    {0xB88F0000u, 84u}, // peo -> Xpeo
-    {0xDC8F0000u, 40u}, // pex -> Latn
-    {0xACAF0000u, 40u}, // pfl -> Latn
+    {0x980F0000u, 44u}, // pag -> Latn
+    {0xAC0F0000u, 65u}, // pal -> Phli
+    {0xB00F0000u, 44u}, // pam -> Latn
+    {0xBC0F0000u, 44u}, // pap -> Latn
+    {0xD00F0000u, 44u}, // pau -> Latn
+    {0xA02F0000u, 44u}, // pbi -> Latn
+    {0x8C4F0000u, 44u}, // pcd -> Latn
+    {0xB04F0000u, 44u}, // pcm -> Latn
+    {0x886F0000u, 44u}, // pdc -> Latn
+    {0xCC6F0000u, 44u}, // pdt -> Latn
+    {0x8C8F0000u, 44u}, // ped -> Latn
+    {0xB88F0000u, 92u}, // peo -> Xpeo
+    {0xDC8F0000u, 44u}, // pex -> Latn
+    {0xACAF0000u, 44u}, // pfl -> Latn
     {0xACEF0000u,  1u}, // phl -> Arab
-    {0xB4EF0000u, 61u}, // phn -> Phnx
-    {0xAD0F0000u, 40u}, // pil -> Latn
-    {0xBD0F0000u, 40u}, // pip -> Latn
+    {0xB4EF0000u, 66u}, // phn -> Phnx
+    {0xAD0F0000u, 44u}, // pil -> Latn
+    {0xBD0F0000u, 44u}, // pip -> Latn
     {0x814F0000u,  8u}, // pka -> Brah
-    {0xB94F0000u, 40u}, // pko -> Latn
-    {0x706C0000u, 40u}, // pl -> Latn
-    {0x816F0000u, 40u}, // pla -> Latn
-    {0xC98F0000u, 40u}, // pms -> Latn
-    {0x99AF0000u, 40u}, // png -> Latn
-    {0xB5AF0000u, 40u}, // pnn -> Latn
-    {0xCDAF0000u, 21u}, // pnt -> Grek
-    {0xB5CF0000u, 40u}, // pon -> Latn
-    {0xB9EF0000u, 40u}, // ppo -> Latn
-    {0x822F0000u, 34u}, // pra -> Khar
+    {0xB94F0000u, 44u}, // pko -> Latn
+    {0x706C0000u, 44u}, // pl -> Latn
+    {0x816F0000u, 44u}, // pla -> Latn
+    {0xC98F0000u, 44u}, // pms -> Latn
+    {0x99AF0000u, 44u}, // png -> Latn
+    {0xB5AF0000u, 44u}, // pnn -> Latn
+    {0xCDAF0000u, 24u}, // pnt -> Grek
+    {0xB5CF0000u, 44u}, // pon -> Latn
+    {0xB9EF0000u, 44u}, // ppo -> Latn
+    {0x822F0000u, 38u}, // pra -> Khar
     {0x8E2F0000u,  1u}, // prd -> Arab
-    {0x9A2F0000u, 40u}, // prg -> Latn
+    {0x9A2F0000u, 44u}, // prg -> Latn
     {0x70730000u,  1u}, // ps -> Arab
-    {0xCA4F0000u, 40u}, // pss -> Latn
-    {0x70740000u, 40u}, // pt -> Latn
-    {0xBE6F0000u, 40u}, // ptp -> Latn
-    {0xD28F0000u, 40u}, // puu -> Latn
-    {0x82CF0000u, 40u}, // pwa -> Latn
-    {0x71750000u, 40u}, // qu -> Latn
-    {0x8A900000u, 40u}, // quc -> Latn
-    {0x9A900000u, 40u}, // qug -> Latn
-    {0xA0110000u, 40u}, // rai -> Latn
-    {0xA4110000u, 16u}, // raj -> Deva
-    {0xB8110000u, 40u}, // rao -> Latn
-    {0x94510000u, 40u}, // rcf -> Latn
-    {0xA4910000u, 40u}, // rej -> Latn
-    {0xAC910000u, 40u}, // rel -> Latn
-    {0xC8910000u, 40u}, // res -> Latn
-    {0xB4D10000u, 40u}, // rgn -> Latn
+    {0xCA4F0000u, 44u}, // pss -> Latn
+    {0x70740000u, 44u}, // pt -> Latn
+    {0xBE6F0000u, 44u}, // ptp -> Latn
+    {0xD28F0000u, 44u}, // puu -> Latn
+    {0x82CF0000u, 44u}, // pwa -> Latn
+    {0x71750000u, 44u}, // qu -> Latn
+    {0x8A900000u, 44u}, // quc -> Latn
+    {0x9A900000u, 44u}, // qug -> Latn
+    {0xA0110000u, 44u}, // rai -> Latn
+    {0xA4110000u, 17u}, // raj -> Deva
+    {0xB8110000u, 44u}, // rao -> Latn
+    {0x94510000u, 44u}, // rcf -> Latn
+    {0xA4910000u, 44u}, // rej -> Latn
+    {0xAC910000u, 44u}, // rel -> Latn
+    {0xC8910000u, 44u}, // res -> Latn
+    {0xB4D10000u, 44u}, // rgn -> Latn
     {0x98F10000u,  1u}, // rhg -> Arab
-    {0x81110000u, 40u}, // ria -> Latn
-    {0x95110000u, 78u}, // rif -> Tfng
-    {0x95114E4Cu, 40u}, // rif-NL -> Latn
-    {0xC9310000u, 16u}, // rjs -> Deva
+    {0x81110000u, 44u}, // ria -> Latn
+    {0x95110000u, 85u}, // rif -> Tfng
+    {0x95114E4Cu, 44u}, // rif-NL -> Latn
+    {0xC9310000u, 17u}, // rjs -> Deva
     {0xCD510000u,  7u}, // rkt -> Beng
-    {0x726D0000u, 40u}, // rm -> Latn
-    {0x95910000u, 40u}, // rmf -> Latn
-    {0xB9910000u, 40u}, // rmo -> Latn
+    {0x726D0000u, 44u}, // rm -> Latn
+    {0x95910000u, 44u}, // rmf -> Latn
+    {0xB9910000u, 44u}, // rmo -> Latn
     {0xCD910000u,  1u}, // rmt -> Arab
-    {0xD1910000u, 40u}, // rmu -> Latn
-    {0x726E0000u, 40u}, // rn -> Latn
-    {0x81B10000u, 40u}, // rna -> Latn
-    {0x99B10000u, 40u}, // rng -> Latn
-    {0x726F0000u, 40u}, // ro -> Latn
-    {0x85D10000u, 40u}, // rob -> Latn
-    {0x95D10000u, 40u}, // rof -> Latn
-    {0xB9D10000u, 40u}, // roo -> Latn
-    {0xBA310000u, 40u}, // rro -> Latn
-    {0xB2710000u, 40u}, // rtm -> Latn
-    {0x72750000u, 15u}, // ru -> Cyrl
-    {0x92910000u, 15u}, // rue -> Cyrl
-    {0x9A910000u, 40u}, // rug -> Latn
-    {0x72770000u, 40u}, // rw -> Latn
-    {0xAAD10000u, 40u}, // rwk -> Latn
-    {0xBAD10000u, 40u}, // rwo -> Latn
-    {0xD3110000u, 33u}, // ryu -> Kana
-    {0x73610000u, 16u}, // sa -> Deva
-    {0x94120000u, 40u}, // saf -> Latn
-    {0x9C120000u, 15u}, // sah -> Cyrl
-    {0xC0120000u, 40u}, // saq -> Latn
-    {0xC8120000u, 40u}, // sas -> Latn
-    {0xCC120000u, 40u}, // sat -> Latn
-    {0xE4120000u, 67u}, // saz -> Saur
-    {0x80320000u, 40u}, // sba -> Latn
-    {0x90320000u, 40u}, // sbe -> Latn
-    {0xBC320000u, 40u}, // sbp -> Latn
-    {0x73630000u, 40u}, // sc -> Latn
-    {0xA8520000u, 16u}, // sck -> Deva
+    {0xD1910000u, 44u}, // rmu -> Latn
+    {0x726E0000u, 44u}, // rn -> Latn
+    {0x81B10000u, 44u}, // rna -> Latn
+    {0x99B10000u, 44u}, // rng -> Latn
+    {0x726F0000u, 44u}, // ro -> Latn
+    {0x85D10000u, 44u}, // rob -> Latn
+    {0x95D10000u, 44u}, // rof -> Latn
+    {0xB9D10000u, 44u}, // roo -> Latn
+    {0xBA310000u, 44u}, // rro -> Latn
+    {0xB2710000u, 44u}, // rtm -> Latn
+    {0x72750000u, 16u}, // ru -> Cyrl
+    {0x92910000u, 16u}, // rue -> Cyrl
+    {0x9A910000u, 44u}, // rug -> Latn
+    {0x72770000u, 44u}, // rw -> Latn
+    {0xAAD10000u, 44u}, // rwk -> Latn
+    {0xBAD10000u, 44u}, // rwo -> Latn
+    {0xD3110000u, 37u}, // ryu -> Kana
+    {0x73610000u, 17u}, // sa -> Deva
+    {0x94120000u, 44u}, // saf -> Latn
+    {0x9C120000u, 16u}, // sah -> Cyrl
+    {0xC0120000u, 44u}, // saq -> Latn
+    {0xC8120000u, 44u}, // sas -> Latn
+    {0xCC120000u, 44u}, // sat -> Latn
+    {0xD4120000u, 44u}, // sav -> Latn
+    {0xE4120000u, 72u}, // saz -> Saur
+    {0x80320000u, 44u}, // sba -> Latn
+    {0x90320000u, 44u}, // sbe -> Latn
+    {0xBC320000u, 44u}, // sbp -> Latn
+    {0x73630000u, 44u}, // sc -> Latn
+    {0xA8520000u, 17u}, // sck -> Deva
     {0xAC520000u,  1u}, // scl -> Arab
-    {0xB4520000u, 40u}, // scn -> Latn
-    {0xB8520000u, 40u}, // sco -> Latn
-    {0xC8520000u, 40u}, // scs -> Latn
+    {0xB4520000u, 44u}, // scn -> Latn
+    {0xB8520000u, 44u}, // sco -> Latn
+    {0xC8520000u, 44u}, // scs -> Latn
     {0x73640000u,  1u}, // sd -> Arab
-    {0x88720000u, 40u}, // sdc -> Latn
+    {0x88720000u, 44u}, // sdc -> Latn
     {0x9C720000u,  1u}, // sdh -> Arab
-    {0x73650000u, 40u}, // se -> Latn
-    {0x94920000u, 40u}, // sef -> Latn
-    {0x9C920000u, 40u}, // seh -> Latn
-    {0xA0920000u, 40u}, // sei -> Latn
-    {0xC8920000u, 40u}, // ses -> Latn
-    {0x73670000u, 40u}, // sg -> Latn
-    {0x80D20000u, 55u}, // sga -> Ogam
-    {0xC8D20000u, 40u}, // sgs -> Latn
-    {0xD8D20000u, 18u}, // sgw -> Ethi
-    {0xE4D20000u, 40u}, // sgz -> Latn
-    {0x73680000u, 40u}, // sh -> Latn
-    {0xA0F20000u, 78u}, // shi -> Tfng
-    {0xA8F20000u, 40u}, // shk -> Latn
-    {0xB4F20000u, 52u}, // shn -> Mymr
+    {0x73650000u, 44u}, // se -> Latn
+    {0x94920000u, 44u}, // sef -> Latn
+    {0x9C920000u, 44u}, // seh -> Latn
+    {0xA0920000u, 44u}, // sei -> Latn
+    {0xC8920000u, 44u}, // ses -> Latn
+    {0x73670000u, 44u}, // sg -> Latn
+    {0x80D20000u, 60u}, // sga -> Ogam
+    {0xC8D20000u, 44u}, // sgs -> Latn
+    {0xD8D20000u, 19u}, // sgw -> Ethi
+    {0xE4D20000u, 44u}, // sgz -> Latn
+    {0x73680000u, 44u}, // sh -> Latn
+    {0xA0F20000u, 85u}, // shi -> Tfng
+    {0xA8F20000u, 44u}, // shk -> Latn
+    {0xB4F20000u, 56u}, // shn -> Mymr
     {0xD0F20000u,  1u}, // shu -> Arab
-    {0x73690000u, 69u}, // si -> Sinh
-    {0x8D120000u, 40u}, // sid -> Latn
-    {0x99120000u, 40u}, // sig -> Latn
-    {0xAD120000u, 40u}, // sil -> Latn
-    {0xB1120000u, 40u}, // sim -> Latn
-    {0xC5320000u, 40u}, // sjr -> Latn
-    {0x736B0000u, 40u}, // sk -> Latn
-    {0x89520000u, 40u}, // skc -> Latn
+    {0x73690000u, 74u}, // si -> Sinh
+    {0x8D120000u, 44u}, // sid -> Latn
+    {0x99120000u, 44u}, // sig -> Latn
+    {0xAD120000u, 44u}, // sil -> Latn
+    {0xB1120000u, 44u}, // sim -> Latn
+    {0xC5320000u, 44u}, // sjr -> Latn
+    {0x736B0000u, 44u}, // sk -> Latn
+    {0x89520000u, 44u}, // skc -> Latn
     {0xC5520000u,  1u}, // skr -> Arab
-    {0xC9520000u, 40u}, // sks -> Latn
-    {0x736C0000u, 40u}, // sl -> Latn
-    {0x8D720000u, 40u}, // sld -> Latn
-    {0xA1720000u, 40u}, // sli -> Latn
-    {0xAD720000u, 40u}, // sll -> Latn
-    {0xE1720000u, 40u}, // sly -> Latn
-    {0x736D0000u, 40u}, // sm -> Latn
-    {0x81920000u, 40u}, // sma -> Latn
-    {0xA5920000u, 40u}, // smj -> Latn
-    {0xB5920000u, 40u}, // smn -> Latn
-    {0xBD920000u, 65u}, // smp -> Samr
-    {0xC1920000u, 40u}, // smq -> Latn
-    {0xC9920000u, 40u}, // sms -> Latn
-    {0x736E0000u, 40u}, // sn -> Latn
-    {0x89B20000u, 40u}, // snc -> Latn
-    {0xA9B20000u, 40u}, // snk -> Latn
-    {0xBDB20000u, 40u}, // snp -> Latn
-    {0xDDB20000u, 40u}, // snx -> Latn
-    {0xE1B20000u, 40u}, // sny -> Latn
-    {0x736F0000u, 40u}, // so -> Latn
-    {0xA9D20000u, 40u}, // sok -> Latn
-    {0xC1D20000u, 40u}, // soq -> Latn
-    {0xD1D20000u, 80u}, // sou -> Thai
-    {0xE1D20000u, 40u}, // soy -> Latn
-    {0x8DF20000u, 40u}, // spd -> Latn
-    {0xADF20000u, 40u}, // spl -> Latn
-    {0xC9F20000u, 40u}, // sps -> Latn
-    {0x73710000u, 40u}, // sq -> Latn
-    {0x73720000u, 15u}, // sr -> Cyrl
-    {0x73724D45u, 40u}, // sr-ME -> Latn
-    {0x7372524Fu, 40u}, // sr-RO -> Latn
-    {0x73725255u, 40u}, // sr-RU -> Latn
-    {0x73725452u, 40u}, // sr-TR -> Latn
-    {0x86320000u, 70u}, // srb -> Sora
-    {0xB6320000u, 40u}, // srn -> Latn
-    {0xC6320000u, 40u}, // srr -> Latn
-    {0xDE320000u, 16u}, // srx -> Deva
-    {0x73730000u, 40u}, // ss -> Latn
-    {0x8E520000u, 40u}, // ssd -> Latn
-    {0x9A520000u, 40u}, // ssg -> Latn
-    {0xE2520000u, 40u}, // ssy -> Latn
-    {0x73740000u, 40u}, // st -> Latn
-    {0xAA720000u, 40u}, // stk -> Latn
-    {0xC2720000u, 40u}, // stq -> Latn
-    {0x73750000u, 40u}, // su -> Latn
-    {0x82920000u, 40u}, // sua -> Latn
-    {0x92920000u, 40u}, // sue -> Latn
-    {0xAA920000u, 40u}, // suk -> Latn
-    {0xC6920000u, 40u}, // sur -> Latn
-    {0xCA920000u, 40u}, // sus -> Latn
-    {0x73760000u, 40u}, // sv -> Latn
-    {0x73770000u, 40u}, // sw -> Latn
+    {0xC9520000u, 44u}, // sks -> Latn
+    {0x736C0000u, 44u}, // sl -> Latn
+    {0x8D720000u, 44u}, // sld -> Latn
+    {0xA1720000u, 44u}, // sli -> Latn
+    {0xAD720000u, 44u}, // sll -> Latn
+    {0xE1720000u, 44u}, // sly -> Latn
+    {0x736D0000u, 44u}, // sm -> Latn
+    {0x81920000u, 44u}, // sma -> Latn
+    {0xA5920000u, 44u}, // smj -> Latn
+    {0xB5920000u, 44u}, // smn -> Latn
+    {0xBD920000u, 70u}, // smp -> Samr
+    {0xC1920000u, 44u}, // smq -> Latn
+    {0xC9920000u, 44u}, // sms -> Latn
+    {0x736E0000u, 44u}, // sn -> Latn
+    {0x89B20000u, 44u}, // snc -> Latn
+    {0xA9B20000u, 44u}, // snk -> Latn
+    {0xBDB20000u, 44u}, // snp -> Latn
+    {0xDDB20000u, 44u}, // snx -> Latn
+    {0xE1B20000u, 44u}, // sny -> Latn
+    {0x736F0000u, 44u}, // so -> Latn
+    {0x99D20000u, 75u}, // sog -> Sogd
+    {0xA9D20000u, 44u}, // sok -> Latn
+    {0xC1D20000u, 44u}, // soq -> Latn
+    {0xD1D20000u, 87u}, // sou -> Thai
+    {0xE1D20000u, 44u}, // soy -> Latn
+    {0x8DF20000u, 44u}, // spd -> Latn
+    {0xADF20000u, 44u}, // spl -> Latn
+    {0xC9F20000u, 44u}, // sps -> Latn
+    {0x73710000u, 44u}, // sq -> Latn
+    {0x73720000u, 16u}, // sr -> Cyrl
+    {0x73724D45u, 44u}, // sr-ME -> Latn
+    {0x7372524Fu, 44u}, // sr-RO -> Latn
+    {0x73725255u, 44u}, // sr-RU -> Latn
+    {0x73725452u, 44u}, // sr-TR -> Latn
+    {0x86320000u, 76u}, // srb -> Sora
+    {0xB6320000u, 44u}, // srn -> Latn
+    {0xC6320000u, 44u}, // srr -> Latn
+    {0xDE320000u, 17u}, // srx -> Deva
+    {0x73730000u, 44u}, // ss -> Latn
+    {0x8E520000u, 44u}, // ssd -> Latn
+    {0x9A520000u, 44u}, // ssg -> Latn
+    {0xE2520000u, 44u}, // ssy -> Latn
+    {0x73740000u, 44u}, // st -> Latn
+    {0xAA720000u, 44u}, // stk -> Latn
+    {0xC2720000u, 44u}, // stq -> Latn
+    {0x73750000u, 44u}, // su -> Latn
+    {0x82920000u, 44u}, // sua -> Latn
+    {0x92920000u, 44u}, // sue -> Latn
+    {0xAA920000u, 44u}, // suk -> Latn
+    {0xC6920000u, 44u}, // sur -> Latn
+    {0xCA920000u, 44u}, // sus -> Latn
+    {0x73760000u, 44u}, // sv -> Latn
+    {0x73770000u, 44u}, // sw -> Latn
     {0x86D20000u,  1u}, // swb -> Arab
-    {0x8AD20000u, 40u}, // swc -> Latn
-    {0x9AD20000u, 40u}, // swg -> Latn
-    {0xBED20000u, 40u}, // swp -> Latn
-    {0xD6D20000u, 16u}, // swv -> Deva
-    {0xB6F20000u, 40u}, // sxn -> Latn
-    {0xDAF20000u, 40u}, // sxw -> Latn
+    {0x8AD20000u, 44u}, // swc -> Latn
+    {0x9AD20000u, 44u}, // swg -> Latn
+    {0xBED20000u, 44u}, // swp -> Latn
+    {0xD6D20000u, 17u}, // swv -> Deva
+    {0xB6F20000u, 44u}, // sxn -> Latn
+    {0xDAF20000u, 44u}, // sxw -> Latn
     {0xAF120000u,  7u}, // syl -> Beng
-    {0xC7120000u, 71u}, // syr -> Syrc
-    {0xAF320000u, 40u}, // szl -> Latn
-    {0x74610000u, 74u}, // ta -> Taml
-    {0xA4130000u, 16u}, // taj -> Deva
-    {0xAC130000u, 40u}, // tal -> Latn
-    {0xB4130000u, 40u}, // tan -> Latn
-    {0xC0130000u, 40u}, // taq -> Latn
-    {0x88330000u, 40u}, // tbc -> Latn
-    {0x8C330000u, 40u}, // tbd -> Latn
-    {0x94330000u, 40u}, // tbf -> Latn
-    {0x98330000u, 40u}, // tbg -> Latn
-    {0xB8330000u, 40u}, // tbo -> Latn
-    {0xD8330000u, 40u}, // tbw -> Latn
-    {0xE4330000u, 40u}, // tbz -> Latn
-    {0xA0530000u, 40u}, // tci -> Latn
-    {0xE0530000u, 36u}, // tcy -> Knda
-    {0x8C730000u, 72u}, // tdd -> Tale
-    {0x98730000u, 16u}, // tdg -> Deva
-    {0x9C730000u, 16u}, // tdh -> Deva
-    {0x74650000u, 77u}, // te -> Telu
-    {0x8C930000u, 40u}, // ted -> Latn
-    {0xB0930000u, 40u}, // tem -> Latn
-    {0xB8930000u, 40u}, // teo -> Latn
-    {0xCC930000u, 40u}, // tet -> Latn
-    {0xA0B30000u, 40u}, // tfi -> Latn
-    {0x74670000u, 15u}, // tg -> Cyrl
+    {0xC7120000u, 78u}, // syr -> Syrc
+    {0xAF320000u, 44u}, // szl -> Latn
+    {0x74610000u, 81u}, // ta -> Taml
+    {0xA4130000u, 17u}, // taj -> Deva
+    {0xAC130000u, 44u}, // tal -> Latn
+    {0xB4130000u, 44u}, // tan -> Latn
+    {0xC0130000u, 44u}, // taq -> Latn
+    {0x88330000u, 44u}, // tbc -> Latn
+    {0x8C330000u, 44u}, // tbd -> Latn
+    {0x94330000u, 44u}, // tbf -> Latn
+    {0x98330000u, 44u}, // tbg -> Latn
+    {0xB8330000u, 44u}, // tbo -> Latn
+    {0xD8330000u, 44u}, // tbw -> Latn
+    {0xE4330000u, 44u}, // tbz -> Latn
+    {0xA0530000u, 44u}, // tci -> Latn
+    {0xE0530000u, 40u}, // tcy -> Knda
+    {0x8C730000u, 79u}, // tdd -> Tale
+    {0x98730000u, 17u}, // tdg -> Deva
+    {0x9C730000u, 17u}, // tdh -> Deva
+    {0x74650000u, 84u}, // te -> Telu
+    {0x8C930000u, 44u}, // ted -> Latn
+    {0xB0930000u, 44u}, // tem -> Latn
+    {0xB8930000u, 44u}, // teo -> Latn
+    {0xCC930000u, 44u}, // tet -> Latn
+    {0xA0B30000u, 44u}, // tfi -> Latn
+    {0x74670000u, 16u}, // tg -> Cyrl
     {0x7467504Bu,  1u}, // tg-PK -> Arab
-    {0x88D30000u, 40u}, // tgc -> Latn
-    {0xB8D30000u, 40u}, // tgo -> Latn
-    {0xD0D30000u, 40u}, // tgu -> Latn
-    {0x74680000u, 80u}, // th -> Thai
-    {0xACF30000u, 16u}, // thl -> Deva
-    {0xC0F30000u, 16u}, // thq -> Deva
-    {0xC4F30000u, 16u}, // thr -> Deva
-    {0x74690000u, 18u}, // ti -> Ethi
-    {0x95130000u, 40u}, // tif -> Latn
-    {0x99130000u, 18u}, // tig -> Ethi
-    {0xA9130000u, 40u}, // tik -> Latn
-    {0xB1130000u, 40u}, // tim -> Latn
-    {0xB9130000u, 40u}, // tio -> Latn
-    {0xD5130000u, 40u}, // tiv -> Latn
-    {0x746B0000u, 40u}, // tk -> Latn
-    {0xAD530000u, 40u}, // tkl -> Latn
-    {0xC5530000u, 40u}, // tkr -> Latn
-    {0xCD530000u, 16u}, // tkt -> Deva
-    {0x746C0000u, 40u}, // tl -> Latn
-    {0x95730000u, 40u}, // tlf -> Latn
-    {0xDD730000u, 40u}, // tlx -> Latn
-    {0xE1730000u, 40u}, // tly -> Latn
-    {0x9D930000u, 40u}, // tmh -> Latn
-    {0xE1930000u, 40u}, // tmy -> Latn
-    {0x746E0000u, 40u}, // tn -> Latn
-    {0x9DB30000u, 40u}, // tnh -> Latn
-    {0x746F0000u, 40u}, // to -> Latn
-    {0x95D30000u, 40u}, // tof -> Latn
-    {0x99D30000u, 40u}, // tog -> Latn
-    {0xC1D30000u, 40u}, // toq -> Latn
-    {0xA1F30000u, 40u}, // tpi -> Latn
-    {0xB1F30000u, 40u}, // tpm -> Latn
-    {0xE5F30000u, 40u}, // tpz -> Latn
-    {0xBA130000u, 40u}, // tqo -> Latn
-    {0x74720000u, 40u}, // tr -> Latn
-    {0xD2330000u, 40u}, // tru -> Latn
-    {0xD6330000u, 40u}, // trv -> Latn
+    {0x88D30000u, 44u}, // tgc -> Latn
+    {0xB8D30000u, 44u}, // tgo -> Latn
+    {0xD0D30000u, 44u}, // tgu -> Latn
+    {0x74680000u, 87u}, // th -> Thai
+    {0xACF30000u, 17u}, // thl -> Deva
+    {0xC0F30000u, 17u}, // thq -> Deva
+    {0xC4F30000u, 17u}, // thr -> Deva
+    {0x74690000u, 19u}, // ti -> Ethi
+    {0x95130000u, 44u}, // tif -> Latn
+    {0x99130000u, 19u}, // tig -> Ethi
+    {0xA9130000u, 44u}, // tik -> Latn
+    {0xB1130000u, 44u}, // tim -> Latn
+    {0xB9130000u, 44u}, // tio -> Latn
+    {0xD5130000u, 44u}, // tiv -> Latn
+    {0x746B0000u, 44u}, // tk -> Latn
+    {0xAD530000u, 44u}, // tkl -> Latn
+    {0xC5530000u, 44u}, // tkr -> Latn
+    {0xCD530000u, 17u}, // tkt -> Deva
+    {0x746C0000u, 44u}, // tl -> Latn
+    {0x95730000u, 44u}, // tlf -> Latn
+    {0xDD730000u, 44u}, // tlx -> Latn
+    {0xE1730000u, 44u}, // tly -> Latn
+    {0x9D930000u, 44u}, // tmh -> Latn
+    {0xE1930000u, 44u}, // tmy -> Latn
+    {0x746E0000u, 44u}, // tn -> Latn
+    {0x9DB30000u, 44u}, // tnh -> Latn
+    {0x746F0000u, 44u}, // to -> Latn
+    {0x95D30000u, 44u}, // tof -> Latn
+    {0x99D30000u, 44u}, // tog -> Latn
+    {0xC1D30000u, 44u}, // toq -> Latn
+    {0xA1F30000u, 44u}, // tpi -> Latn
+    {0xB1F30000u, 44u}, // tpm -> Latn
+    {0xE5F30000u, 44u}, // tpz -> Latn
+    {0xBA130000u, 44u}, // tqo -> Latn
+    {0x74720000u, 44u}, // tr -> Latn
+    {0xD2330000u, 44u}, // tru -> Latn
+    {0xD6330000u, 44u}, // trv -> Latn
     {0xDA330000u,  1u}, // trw -> Arab
-    {0x74730000u, 40u}, // ts -> Latn
-    {0x8E530000u, 21u}, // tsd -> Grek
-    {0x96530000u, 16u}, // tsf -> Deva
-    {0x9A530000u, 40u}, // tsg -> Latn
-    {0xA6530000u, 81u}, // tsj -> Tibt
-    {0xDA530000u, 40u}, // tsw -> Latn
-    {0x74740000u, 15u}, // tt -> Cyrl
-    {0x8E730000u, 40u}, // ttd -> Latn
-    {0x92730000u, 40u}, // tte -> Latn
-    {0xA6730000u, 40u}, // ttj -> Latn
-    {0xC6730000u, 40u}, // ttr -> Latn
-    {0xCA730000u, 80u}, // tts -> Thai
-    {0xCE730000u, 40u}, // ttt -> Latn
-    {0x9E930000u, 40u}, // tuh -> Latn
-    {0xAE930000u, 40u}, // tul -> Latn
-    {0xB2930000u, 40u}, // tum -> Latn
-    {0xC2930000u, 40u}, // tuq -> Latn
-    {0x8EB30000u, 40u}, // tvd -> Latn
-    {0xAEB30000u, 40u}, // tvl -> Latn
-    {0xD2B30000u, 40u}, // tvu -> Latn
-    {0x9ED30000u, 40u}, // twh -> Latn
-    {0xC2D30000u, 40u}, // twq -> Latn
-    {0x9AF30000u, 75u}, // txg -> Tang
-    {0x74790000u, 40u}, // ty -> Latn
-    {0x83130000u, 40u}, // tya -> Latn
-    {0xD7130000u, 15u}, // tyv -> Cyrl
-    {0xB3330000u, 40u}, // tzm -> Latn
-    {0xD0340000u, 40u}, // ubu -> Latn
-    {0xB0740000u, 15u}, // udm -> Cyrl
+    {0x74730000u, 44u}, // ts -> Latn
+    {0x8E530000u, 24u}, // tsd -> Grek
+    {0x96530000u, 17u}, // tsf -> Deva
+    {0x9A530000u, 44u}, // tsg -> Latn
+    {0xA6530000u, 88u}, // tsj -> Tibt
+    {0xDA530000u, 44u}, // tsw -> Latn
+    {0x74740000u, 16u}, // tt -> Cyrl
+    {0x8E730000u, 44u}, // ttd -> Latn
+    {0x92730000u, 44u}, // tte -> Latn
+    {0xA6730000u, 44u}, // ttj -> Latn
+    {0xC6730000u, 44u}, // ttr -> Latn
+    {0xCA730000u, 87u}, // tts -> Thai
+    {0xCE730000u, 44u}, // ttt -> Latn
+    {0x9E930000u, 44u}, // tuh -> Latn
+    {0xAE930000u, 44u}, // tul -> Latn
+    {0xB2930000u, 44u}, // tum -> Latn
+    {0xC2930000u, 44u}, // tuq -> Latn
+    {0x8EB30000u, 44u}, // tvd -> Latn
+    {0xAEB30000u, 44u}, // tvl -> Latn
+    {0xD2B30000u, 44u}, // tvu -> Latn
+    {0x9ED30000u, 44u}, // twh -> Latn
+    {0xC2D30000u, 44u}, // twq -> Latn
+    {0x9AF30000u, 82u}, // txg -> Tang
+    {0x74790000u, 44u}, // ty -> Latn
+    {0x83130000u, 44u}, // tya -> Latn
+    {0xD7130000u, 16u}, // tyv -> Cyrl
+    {0xB3330000u, 44u}, // tzm -> Latn
+    {0xD0340000u, 44u}, // ubu -> Latn
+    {0xB0740000u, 16u}, // udm -> Cyrl
     {0x75670000u,  1u}, // ug -> Arab
-    {0x75674B5Au, 15u}, // ug-KZ -> Cyrl
-    {0x75674D4Eu, 15u}, // ug-MN -> Cyrl
-    {0x80D40000u, 82u}, // uga -> Ugar
-    {0x756B0000u, 15u}, // uk -> Cyrl
-    {0xA1740000u, 40u}, // uli -> Latn
-    {0x85940000u, 40u}, // umb -> Latn
+    {0x75674B5Au, 16u}, // ug-KZ -> Cyrl
+    {0x75674D4Eu, 16u}, // ug-MN -> Cyrl
+    {0x80D40000u, 89u}, // uga -> Ugar
+    {0x756B0000u, 16u}, // uk -> Cyrl
+    {0xA1740000u, 44u}, // uli -> Latn
+    {0x85940000u, 44u}, // umb -> Latn
     {0xC5B40000u,  7u}, // unr -> Beng
-    {0xC5B44E50u, 16u}, // unr-NP -> Deva
+    {0xC5B44E50u, 17u}, // unr-NP -> Deva
     {0xDDB40000u,  7u}, // unx -> Beng
     {0x75720000u,  1u}, // ur -> Arab
-    {0xA2340000u, 40u}, // uri -> Latn
-    {0xCE340000u, 40u}, // urt -> Latn
-    {0xDA340000u, 40u}, // urw -> Latn
-    {0x82540000u, 40u}, // usa -> Latn
-    {0xC6740000u, 40u}, // utr -> Latn
-    {0x9EB40000u, 40u}, // uvh -> Latn
-    {0xAEB40000u, 40u}, // uvl -> Latn
-    {0x757A0000u, 40u}, // uz -> Latn
+    {0xA2340000u, 44u}, // uri -> Latn
+    {0xCE340000u, 44u}, // urt -> Latn
+    {0xDA340000u, 44u}, // urw -> Latn
+    {0x82540000u, 44u}, // usa -> Latn
+    {0xC6740000u, 44u}, // utr -> Latn
+    {0x9EB40000u, 44u}, // uvh -> Latn
+    {0xAEB40000u, 44u}, // uvl -> Latn
+    {0x757A0000u, 44u}, // uz -> Latn
     {0x757A4146u,  1u}, // uz-AF -> Arab
-    {0x757A434Eu, 15u}, // uz-CN -> Cyrl
-    {0x98150000u, 40u}, // vag -> Latn
-    {0xA0150000u, 83u}, // vai -> Vaii
-    {0xB4150000u, 40u}, // van -> Latn
-    {0x76650000u, 40u}, // ve -> Latn
-    {0x88950000u, 40u}, // vec -> Latn
-    {0xBC950000u, 40u}, // vep -> Latn
-    {0x76690000u, 40u}, // vi -> Latn
-    {0x89150000u, 40u}, // vic -> Latn
-    {0xD5150000u, 40u}, // viv -> Latn
-    {0xC9750000u, 40u}, // vls -> Latn
-    {0x95950000u, 40u}, // vmf -> Latn
-    {0xD9950000u, 40u}, // vmw -> Latn
-    {0x766F0000u, 40u}, // vo -> Latn
-    {0xCDD50000u, 40u}, // vot -> Latn
-    {0xBA350000u, 40u}, // vro -> Latn
-    {0xB6950000u, 40u}, // vun -> Latn
-    {0xCE950000u, 40u}, // vut -> Latn
-    {0x77610000u, 40u}, // wa -> Latn
-    {0x90160000u, 40u}, // wae -> Latn
-    {0xA4160000u, 40u}, // waj -> Latn
-    {0xAC160000u, 18u}, // wal -> Ethi
-    {0xB4160000u, 40u}, // wan -> Latn
-    {0xC4160000u, 40u}, // war -> Latn
-    {0xBC360000u, 40u}, // wbp -> Latn
-    {0xC0360000u, 77u}, // wbq -> Telu
-    {0xC4360000u, 16u}, // wbr -> Deva
-    {0xA0560000u, 40u}, // wci -> Latn
-    {0xC4960000u, 40u}, // wer -> Latn
-    {0xA0D60000u, 40u}, // wgi -> Latn
-    {0x98F60000u, 40u}, // whg -> Latn
-    {0x85160000u, 40u}, // wib -> Latn
-    {0xD1160000u, 40u}, // wiu -> Latn
-    {0xD5160000u, 40u}, // wiv -> Latn
-    {0x81360000u, 40u}, // wja -> Latn
-    {0xA1360000u, 40u}, // wji -> Latn
-    {0xC9760000u, 40u}, // wls -> Latn
-    {0xB9960000u, 40u}, // wmo -> Latn
-    {0x89B60000u, 40u}, // wnc -> Latn
+    {0x757A434Eu, 16u}, // uz-CN -> Cyrl
+    {0x98150000u, 44u}, // vag -> Latn
+    {0xA0150000u, 90u}, // vai -> Vaii
+    {0xB4150000u, 44u}, // van -> Latn
+    {0x76650000u, 44u}, // ve -> Latn
+    {0x88950000u, 44u}, // vec -> Latn
+    {0xBC950000u, 44u}, // vep -> Latn
+    {0x76690000u, 44u}, // vi -> Latn
+    {0x89150000u, 44u}, // vic -> Latn
+    {0xD5150000u, 44u}, // viv -> Latn
+    {0xC9750000u, 44u}, // vls -> Latn
+    {0x95950000u, 44u}, // vmf -> Latn
+    {0xD9950000u, 44u}, // vmw -> Latn
+    {0x766F0000u, 44u}, // vo -> Latn
+    {0xCDD50000u, 44u}, // vot -> Latn
+    {0xBA350000u, 44u}, // vro -> Latn
+    {0xB6950000u, 44u}, // vun -> Latn
+    {0xCE950000u, 44u}, // vut -> Latn
+    {0x77610000u, 44u}, // wa -> Latn
+    {0x90160000u, 44u}, // wae -> Latn
+    {0xA4160000u, 44u}, // waj -> Latn
+    {0xAC160000u, 19u}, // wal -> Ethi
+    {0xB4160000u, 44u}, // wan -> Latn
+    {0xC4160000u, 44u}, // war -> Latn
+    {0xBC360000u, 44u}, // wbp -> Latn
+    {0xC0360000u, 84u}, // wbq -> Telu
+    {0xC4360000u, 17u}, // wbr -> Deva
+    {0xA0560000u, 44u}, // wci -> Latn
+    {0xC4960000u, 44u}, // wer -> Latn
+    {0xA0D60000u, 44u}, // wgi -> Latn
+    {0x98F60000u, 44u}, // whg -> Latn
+    {0x85160000u, 44u}, // wib -> Latn
+    {0xD1160000u, 44u}, // wiu -> Latn
+    {0xD5160000u, 44u}, // wiv -> Latn
+    {0x81360000u, 44u}, // wja -> Latn
+    {0xA1360000u, 44u}, // wji -> Latn
+    {0xC9760000u, 44u}, // wls -> Latn
+    {0xB9960000u, 44u}, // wmo -> Latn
+    {0x89B60000u, 44u}, // wnc -> Latn
     {0xA1B60000u,  1u}, // wni -> Arab
-    {0xD1B60000u, 40u}, // wnu -> Latn
-    {0x776F0000u, 40u}, // wo -> Latn
-    {0x85D60000u, 40u}, // wob -> Latn
-    {0xC9D60000u, 40u}, // wos -> Latn
-    {0xCA360000u, 40u}, // wrs -> Latn
-    {0xAA560000u, 40u}, // wsk -> Latn
-    {0xB2760000u, 16u}, // wtm -> Deva
-    {0xD2960000u, 24u}, // wuu -> Hans
-    {0xD6960000u, 40u}, // wuv -> Latn
-    {0x82D60000u, 40u}, // wwa -> Latn
-    {0xD4170000u, 40u}, // xav -> Latn
-    {0xA0370000u, 40u}, // xbi -> Latn
-    {0xC4570000u, 10u}, // xcr -> Cari
-    {0xC8970000u, 40u}, // xes -> Latn
-    {0x78680000u, 40u}, // xh -> Latn
-    {0x81770000u, 40u}, // xla -> Latn
-    {0x89770000u, 44u}, // xlc -> Lyci
-    {0x8D770000u, 45u}, // xld -> Lydi
-    {0x95970000u, 19u}, // xmf -> Geor
-    {0xB5970000u, 47u}, // xmn -> Mani
-    {0xC5970000u, 48u}, // xmr -> Merc
-    {0x81B70000u, 53u}, // xna -> Narb
-    {0xC5B70000u, 16u}, // xnr -> Deva
-    {0x99D70000u, 40u}, // xog -> Latn
-    {0xB5D70000u, 40u}, // xon -> Latn
-    {0xC5F70000u, 63u}, // xpr -> Prti
-    {0x86370000u, 40u}, // xrb -> Latn
-    {0x82570000u, 66u}, // xsa -> Sarb
-    {0xA2570000u, 40u}, // xsi -> Latn
-    {0xB2570000u, 40u}, // xsm -> Latn
-    {0xC6570000u, 16u}, // xsr -> Deva
-    {0x92D70000u, 40u}, // xwe -> Latn
-    {0xB0180000u, 40u}, // yam -> Latn
-    {0xB8180000u, 40u}, // yao -> Latn
-    {0xBC180000u, 40u}, // yap -> Latn
-    {0xC8180000u, 40u}, // yas -> Latn
-    {0xCC180000u, 40u}, // yat -> Latn
-    {0xD4180000u, 40u}, // yav -> Latn
-    {0xE0180000u, 40u}, // yay -> Latn
-    {0xE4180000u, 40u}, // yaz -> Latn
-    {0x80380000u, 40u}, // yba -> Latn
-    {0x84380000u, 40u}, // ybb -> Latn
-    {0xE0380000u, 40u}, // yby -> Latn
-    {0xC4980000u, 40u}, // yer -> Latn
-    {0xC4D80000u, 40u}, // ygr -> Latn
-    {0xD8D80000u, 40u}, // ygw -> Latn
-    {0x79690000u, 27u}, // yi -> Hebr
-    {0xB9580000u, 40u}, // yko -> Latn
-    {0x91780000u, 40u}, // yle -> Latn
-    {0x99780000u, 40u}, // ylg -> Latn
-    {0xAD780000u, 40u}, // yll -> Latn
-    {0xAD980000u, 40u}, // yml -> Latn
-    {0x796F0000u, 40u}, // yo -> Latn
-    {0xB5D80000u, 40u}, // yon -> Latn
-    {0x86380000u, 40u}, // yrb -> Latn
-    {0x92380000u, 40u}, // yre -> Latn
-    {0xAE380000u, 40u}, // yrl -> Latn
-    {0xCA580000u, 40u}, // yss -> Latn
-    {0x82980000u, 40u}, // yua -> Latn
-    {0x92980000u, 25u}, // yue -> Hant
-    {0x9298434Eu, 24u}, // yue-CN -> Hans
-    {0xA6980000u, 40u}, // yuj -> Latn
-    {0xCE980000u, 40u}, // yut -> Latn
-    {0xDA980000u, 40u}, // yuw -> Latn
-    {0x7A610000u, 40u}, // za -> Latn
-    {0x98190000u, 40u}, // zag -> Latn
+    {0xD1B60000u, 44u}, // wnu -> Latn
+    {0x776F0000u, 44u}, // wo -> Latn
+    {0x85D60000u, 44u}, // wob -> Latn
+    {0xC9D60000u, 44u}, // wos -> Latn
+    {0xCA360000u, 44u}, // wrs -> Latn
+    {0x9A560000u, 21u}, // wsg -> Gong
+    {0xAA560000u, 44u}, // wsk -> Latn
+    {0xB2760000u, 17u}, // wtm -> Deva
+    {0xD2960000u, 27u}, // wuu -> Hans
+    {0xD6960000u, 44u}, // wuv -> Latn
+    {0x82D60000u, 44u}, // wwa -> Latn
+    {0xD4170000u, 44u}, // xav -> Latn
+    {0xA0370000u, 44u}, // xbi -> Latn
+    {0xC4570000u, 11u}, // xcr -> Cari
+    {0xC8970000u, 44u}, // xes -> Latn
+    {0x78680000u, 44u}, // xh -> Latn
+    {0x81770000u, 44u}, // xla -> Latn
+    {0x89770000u, 48u}, // xlc -> Lyci
+    {0x8D770000u, 49u}, // xld -> Lydi
+    {0x95970000u, 20u}, // xmf -> Geor
+    {0xB5970000u, 51u}, // xmn -> Mani
+    {0xC5970000u, 52u}, // xmr -> Merc
+    {0x81B70000u, 57u}, // xna -> Narb
+    {0xC5B70000u, 17u}, // xnr -> Deva
+    {0x99D70000u, 44u}, // xog -> Latn
+    {0xB5D70000u, 44u}, // xon -> Latn
+    {0xC5F70000u, 68u}, // xpr -> Prti
+    {0x86370000u, 44u}, // xrb -> Latn
+    {0x82570000u, 71u}, // xsa -> Sarb
+    {0xA2570000u, 44u}, // xsi -> Latn
+    {0xB2570000u, 44u}, // xsm -> Latn
+    {0xC6570000u, 17u}, // xsr -> Deva
+    {0x92D70000u, 44u}, // xwe -> Latn
+    {0xB0180000u, 44u}, // yam -> Latn
+    {0xB8180000u, 44u}, // yao -> Latn
+    {0xBC180000u, 44u}, // yap -> Latn
+    {0xC8180000u, 44u}, // yas -> Latn
+    {0xCC180000u, 44u}, // yat -> Latn
+    {0xD4180000u, 44u}, // yav -> Latn
+    {0xE0180000u, 44u}, // yay -> Latn
+    {0xE4180000u, 44u}, // yaz -> Latn
+    {0x80380000u, 44u}, // yba -> Latn
+    {0x84380000u, 44u}, // ybb -> Latn
+    {0xE0380000u, 44u}, // yby -> Latn
+    {0xC4980000u, 44u}, // yer -> Latn
+    {0xC4D80000u, 44u}, // ygr -> Latn
+    {0xD8D80000u, 44u}, // ygw -> Latn
+    {0x79690000u, 30u}, // yi -> Hebr
+    {0xB9580000u, 44u}, // yko -> Latn
+    {0x91780000u, 44u}, // yle -> Latn
+    {0x99780000u, 44u}, // ylg -> Latn
+    {0xAD780000u, 44u}, // yll -> Latn
+    {0xAD980000u, 44u}, // yml -> Latn
+    {0x796F0000u, 44u}, // yo -> Latn
+    {0xB5D80000u, 44u}, // yon -> Latn
+    {0x86380000u, 44u}, // yrb -> Latn
+    {0x92380000u, 44u}, // yre -> Latn
+    {0xAE380000u, 44u}, // yrl -> Latn
+    {0xCA580000u, 44u}, // yss -> Latn
+    {0x82980000u, 44u}, // yua -> Latn
+    {0x92980000u, 28u}, // yue -> Hant
+    {0x9298434Eu, 27u}, // yue-CN -> Hans
+    {0xA6980000u, 44u}, // yuj -> Latn
+    {0xCE980000u, 44u}, // yut -> Latn
+    {0xDA980000u, 44u}, // yuw -> Latn
+    {0x7A610000u, 44u}, // za -> Latn
+    {0x98190000u, 44u}, // zag -> Latn
     {0xA4790000u,  1u}, // zdj -> Arab
-    {0x80990000u, 40u}, // zea -> Latn
-    {0x9CD90000u, 78u}, // zgh -> Tfng
-    {0x7A680000u, 24u}, // zh -> Hans
-    {0x7A684155u, 25u}, // zh-AU -> Hant
-    {0x7A68424Eu, 25u}, // zh-BN -> Hant
-    {0x7A684742u, 25u}, // zh-GB -> Hant
-    {0x7A684746u, 25u}, // zh-GF -> Hant
-    {0x7A68484Bu, 25u}, // zh-HK -> Hant
-    {0x7A684944u, 25u}, // zh-ID -> Hant
-    {0x7A684D4Fu, 25u}, // zh-MO -> Hant
-    {0x7A684D59u, 25u}, // zh-MY -> Hant
-    {0x7A685041u, 25u}, // zh-PA -> Hant
-    {0x7A685046u, 25u}, // zh-PF -> Hant
-    {0x7A685048u, 25u}, // zh-PH -> Hant
-    {0x7A685352u, 25u}, // zh-SR -> Hant
-    {0x7A685448u, 25u}, // zh-TH -> Hant
-    {0x7A685457u, 25u}, // zh-TW -> Hant
-    {0x7A685553u, 25u}, // zh-US -> Hant
-    {0x7A68564Eu, 25u}, // zh-VN -> Hant
-    {0x81190000u, 40u}, // zia -> Latn
-    {0xB1790000u, 40u}, // zlm -> Latn
-    {0xA1990000u, 40u}, // zmi -> Latn
-    {0x91B90000u, 40u}, // zne -> Latn
-    {0x7A750000u, 40u}, // zu -> Latn
-    {0x83390000u, 40u}, // zza -> Latn
+    {0x80990000u, 44u}, // zea -> Latn
+    {0x9CD90000u, 85u}, // zgh -> Tfng
+    {0x7A680000u, 27u}, // zh -> Hans
+    {0x7A684155u, 28u}, // zh-AU -> Hant
+    {0x7A68424Eu, 28u}, // zh-BN -> Hant
+    {0x7A684742u, 28u}, // zh-GB -> Hant
+    {0x7A684746u, 28u}, // zh-GF -> Hant
+    {0x7A68484Bu, 28u}, // zh-HK -> Hant
+    {0x7A684944u, 28u}, // zh-ID -> Hant
+    {0x7A684D4Fu, 28u}, // zh-MO -> Hant
+    {0x7A684D59u, 28u}, // zh-MY -> Hant
+    {0x7A685041u, 28u}, // zh-PA -> Hant
+    {0x7A685046u, 28u}, // zh-PF -> Hant
+    {0x7A685048u, 28u}, // zh-PH -> Hant
+    {0x7A685352u, 28u}, // zh-SR -> Hant
+    {0x7A685448u, 28u}, // zh-TH -> Hant
+    {0x7A685457u, 28u}, // zh-TW -> Hant
+    {0x7A685553u, 28u}, // zh-US -> Hant
+    {0x7A68564Eu, 28u}, // zh-VN -> Hant
+    {0xDCF90000u, 59u}, // zhx -> Nshu
+    {0x81190000u, 44u}, // zia -> Latn
+    {0xB1790000u, 44u}, // zlm -> Latn
+    {0xA1990000u, 44u}, // zmi -> Latn
+    {0x91B90000u, 44u}, // zne -> Latn
+    {0x7A750000u, 44u}, // zu -> Latn
+    {0x83390000u, 44u}, // zza -> Latn
 });
 
 std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
@@ -1517,6 +1544,7 @@
     0xB5014E474C61746ELLU, // bin_Latn_NG
     0xA521494E44657661LLU, // bjj_Deva_IN
     0xB52149444C61746ELLU, // bjn_Latn_ID
+    0xCD21534E4C61746ELLU, // bjt_Latn_SN
     0xB141434D4C61746ELLU, // bkm_Latn_CM
     0xD14150484C61746ELLU, // bku_Latn_PH
     0xCD61564E54617674LLU, // blt_Tavt_VN
@@ -1546,7 +1574,6 @@
     0x93214D4C4C61746ELLU, // bze_Latn_ML
     0x636145534C61746ELLU, // ca_Latn_ES
     0x9C424E474C61746ELLU, // cch_Latn_NG
-    0xBC42494E42656E67LLU, // ccp_Beng_IN
     0xBC42424443616B6DLLU, // ccp_Cakm_BD
     0x636552554379726CLLU, // ce_Cyrl_RU
     0x848250484C61746ELLU, // ceb_Latn_PH
@@ -1560,10 +1587,12 @@
     0x81224B4841726162LLU, // cja_Arab_KH
     0xB122564E4368616DLLU, // cjm_Cham_VN
     0x8542495141726162LLU, // ckb_Arab_IQ
+    0x99824D4E536F796FLLU, // cmg_Soyo_MN
     0x636F46524C61746ELLU, // co_Latn_FR
     0xBDC24547436F7074LLU, // cop_Copt_EG
     0xC9E250484C61746ELLU, // cps_Latn_PH
     0x6372434143616E73LLU, // cr_Cans_CA
+    0x9E2255414379726CLLU, // crh_Cyrl_UA
     0xA622434143616E73LLU, // crj_Cans_CA
     0xAA22434143616E73LLU, // crk_Cans_CA
     0xAE22434143616E73LLU, // crl_Cans_CA
@@ -1610,6 +1639,7 @@
     0x657345534C61746ELLU, // es_Latn_ES
     0x65734D584C61746ELLU, // es_Latn_MX
     0x657355534C61746ELLU, // es_Latn_US
+    0x9A44494E476F6E6DLLU, // esg_Gonm_IN
     0xD24455534C61746ELLU, // esu_Latn_US
     0x657445454C61746ELLU, // et_Latn_EE
     0xCE6449544974616CLLU, // ett_Ital_IT
@@ -1700,10 +1730,10 @@
     0x687548554C61746ELLU, // hu_Latn_HU
     0x6879414D41726D6ELLU, // hy_Armn_AM
     0x687A4E414C61746ELLU, // hz_Latn_NA
-    0x696146524C61746ELLU, // ia_Latn_FR
     0x80284D594C61746ELLU, // iba_Latn_MY
     0x84284E474C61746ELLU, // ibb_Latn_NG
     0x696449444C61746ELLU, // id_Latn_ID
+    0x90A854474C61746ELLU, // ife_Latn_TG
     0x69674E474C61746ELLU, // ig_Latn_NG
     0x6969434E59696969LLU, // ii_Yiii_CN
     0x696B55534C61746ELLU, // ik_Latn_US
@@ -1764,6 +1794,7 @@
     0x6B6D4B484B686D72LLU, // km_Khmr_KH
     0x858A414F4C61746ELLU, // kmb_Latn_AO
     0x6B6E494E4B6E6461LLU, // kn_Knda_IN
+    0x95AA47574C61746ELLU, // knf_Latn_GW
     0x6B6F4B524B6F7265LLU, // ko_Kore_KR
     0xA1CA52554379726CLLU, // koi_Cyrl_RU
     0xA9CA494E44657661LLU, // kok_Deva_IN
@@ -1854,6 +1885,7 @@
     0x6D694E5A4C61746ELLU, // mi_Latn_NZ
     0xB50C49444C61746ELLU, // min_Latn_ID
     0xC90C495148617472LLU, // mis_Hatr_IQ
+    0xC90C4E474D656466LLU, // mis_Medf_NG
     0x6D6B4D4B4379726CLLU, // mk_Cyrl_MK
     0x6D6C494E4D6C796DLLU, // ml_Mlym_IN
     0xC96C53444C61746ELLU, // mls_Latn_SD
@@ -1877,6 +1909,7 @@
     0xAACC4D4C4C61746ELLU, // mwk_Latn_ML
     0xC6CC494E44657661LLU, // mwr_Deva_IN
     0xD6CC49444C61746ELLU, // mwv_Latn_ID
+    0xDACC5553486D6E70LLU, // mww_Hmnp_US
     0x8AEC5A574C61746ELLU, // mxc_Latn_ZW
     0x6D794D4D4D796D72LLU, // my_Mymr_MM
     0xD70C52554379726CLLU, // myv_Cyrl_RU
@@ -1905,6 +1938,7 @@
     0x998D434D4C61746ELLU, // nmg_Latn_CM
     0x6E6E4E4F4C61746ELLU, // nn_Latn_NO
     0x9DAD434D4C61746ELLU, // nnh_Latn_CM
+    0xBDAD494E5763686FLLU, // nnp_Wcho_IN
     0x6E6F4E4F4C61746ELLU, // no_Latn_NO
     0x8DCD54484C616E61LLU, // nod_Lana_TH
     0x91CD494E44657661LLU, // noe_Deva_IN
@@ -1959,6 +1993,7 @@
     0x945152454C61746ELLU, // rcf_Latn_RE
     0xA49149444C61746ELLU, // rej_Latn_ID
     0xB4D149544C61746ELLU, // rgn_Latn_IT
+    0x98F14D4D41726162LLU, // rhg_Arab_MM
     0x8111494E4C61746ELLU, // ria_Latn_IN
     0x95114D4154666E67LLU, // rif_Tfng_MA
     0xC9314E5044657661LLU, // rjs_Deva_NP
@@ -1986,6 +2021,7 @@
     0xC0124B454C61746ELLU, // saq_Latn_KE
     0xC81249444C61746ELLU, // sas_Latn_ID
     0xCC12494E4C61746ELLU, // sat_Latn_IN
+    0xD412534E4C61746ELLU, // sav_Latn_SN
     0xE412494E53617572LLU, // saz_Saur_IN
     0xBC32545A4C61746ELLU, // sbp_Latn_TZ
     0x736349544C61746ELLU, // sc_Latn_IT
@@ -2025,6 +2061,7 @@
     0x736E5A574C61746ELLU, // sn_Latn_ZW
     0xA9B24D4C4C61746ELLU, // snk_Latn_ML
     0x736F534F4C61746ELLU, // so_Latn_SO
+    0x99D2555A536F6764LLU, // sog_Sogd_UZ
     0xD1D2544854686169LLU, // sou_Thai_TH
     0x7371414C4C61746ELLU, // sq_Latn_AL
     0x737252534379726CLLU, // sr_Cyrl_RS
@@ -2135,6 +2172,7 @@
     0xC97657464C61746ELLU, // wls_Latn_WF
     0xA1B64B4D41726162LLU, // wni_Arab_KM
     0x776F534E4C61746ELLU, // wo_Latn_SN
+    0x9A56494E476F6E67LLU, // wsg_Gong_IN
     0xB276494E44657661LLU, // wtm_Deva_IN
     0xD296434E48616E73LLU, // wuu_Hans_CN
     0xD41742524C61746ELLU, // xav_Latn_BR
@@ -2169,6 +2207,7 @@
     0x7A68545748616E62LLU, // zh_Hanb_TW
     0x7A68434E48616E73LLU, // zh_Hans_CN
     0x7A68545748616E74LLU, // zh_Hant_TW
+    0xDCF9434E4E736875LLU, // zhx_Nshu_CN
     0xB17954474C61746ELLU, // zlm_Latn_TG
     0xA1994D594C61746ELLU, // zmi_Latn_MY
     0x7A755A414C61746ELLU, // zu_Latn_ZA
@@ -2194,7 +2233,7 @@
     {0x656E4154u, 0x656E80A1u}, // en-AT -> en-150
     {0x656E4155u, 0x656E8400u}, // en-AU -> en-001
     {0x656E4242u, 0x656E8400u}, // en-BB -> en-001
-    {0x656E4245u, 0x656E8400u}, // en-BE -> en-001
+    {0x656E4245u, 0x656E80A1u}, // en-BE -> en-150
     {0x656E424Du, 0x656E8400u}, // en-BM -> en-001
     {0x656E4253u, 0x656E8400u}, // en-BS -> en-001
     {0x656E4257u, 0x656E8400u}, // en-BW -> en-001
@@ -2285,6 +2324,7 @@
     {0x65734152u, 0x6573A424u}, // es-AR -> es-419
     {0x6573424Fu, 0x6573A424u}, // es-BO -> es-419
     {0x65734252u, 0x6573A424u}, // es-BR -> es-419
+    {0x6573425Au, 0x6573A424u}, // es-BZ -> es-419
     {0x6573434Cu, 0x6573A424u}, // es-CL -> es-419
     {0x6573434Fu, 0x6573A424u}, // es-CO -> es-419
     {0x65734352u, 0x6573A424u}, // es-CR -> es-419
@@ -2315,6 +2355,10 @@
     {0x7074544Cu, 0x70745054u}, // pt-TL -> pt-PT
 });
 
+const std::unordered_map<uint32_t, uint32_t> ___B_PARENTS({
+    {0x61725842u, 0x61729420u}, // ar-XB -> ar-015
+});
+
 const struct {
     const char script[4];
     const std::unordered_map<uint32_t, uint32_t>* map;
@@ -2322,6 +2366,7 @@
     {{'A', 'r', 'a', 'b'}, &ARAB_PARENTS},
     {{'H', 'a', 'n', 't'}, &HANT_PARENTS},
     {{'L', 'a', 't', 'n'}, &LATN_PARENTS},
+    {{'~', '~', '~', 'B'}, &___B_PARENTS},
 };
 
 const size_t MAX_PARENT_DEPTH = 3;
diff --git a/media/Android.bp b/media/Android.bp
index e2bdad1..75ccb22 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -77,20 +77,13 @@
     path: "apex/java"
 }
 
-metalava_updatable_media_args = " --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\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) "
-
 droidstubs {
     name: "updatable-media-stubs",
     srcs: [
         ":updatable-media-srcs",
         ":framework-media-annotation-srcs",
     ],
-    args: metalava_updatable_media_args,
+    defaults: [ "framework-module-stubs-defaults-systemapi" ],
     aidl: {
         // TODO(b/135922046) remove this
         include_dirs: ["frameworks/base/core/java"],
@@ -109,3 +102,67 @@
     srcs: [":framework-media-annotation-srcs"],
     installable: false,
 }
+
+aidl_interface {
+    name: "audio_common-aidl",
+    local_include_dir: "java",
+    srcs: [
+        "java/android/media/audio/common/AudioChannelMask.aidl",
+        "java/android/media/audio/common/AudioConfig.aidl",
+        "java/android/media/audio/common/AudioFormat.aidl",
+        "java/android/media/audio/common/AudioOffloadInfo.aidl",
+        "java/android/media/audio/common/AudioStreamType.aidl",
+        "java/android/media/audio/common/AudioUsage.aidl",
+    ],
+    backend:
+    {
+        cpp: {
+            enabled: true,
+        },
+        java: {
+            // Already generated as part of the entire media java library.
+            enabled: false,
+        },
+    },
+}
+
+aidl_interface {
+    name: "soundtrigger_middleware-aidl",
+    local_include_dir: "java",
+    srcs: [
+        "java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl",
+        "java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl",
+        "java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl",
+        "java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl",
+        "java/android/media/soundtrigger_middleware/ModelParameter.aidl",
+        "java/android/media/soundtrigger_middleware/ModelParameterRange.aidl",
+        "java/android/media/soundtrigger_middleware/Phrase.aidl",
+        "java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl",
+        "java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl",
+        "java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl",
+        "java/android/media/soundtrigger_middleware/RecognitionConfig.aidl",
+        "java/android/media/soundtrigger_middleware/RecognitionEvent.aidl",
+        "java/android/media/soundtrigger_middleware/RecognitionMode.aidl",
+        "java/android/media/soundtrigger_middleware/RecognitionStatus.aidl",
+        "java/android/media/soundtrigger_middleware/SoundModel.aidl",
+        "java/android/media/soundtrigger_middleware/SoundModelType.aidl",
+        "java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl",
+        "java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl",
+        "java/android/media/soundtrigger_middleware/Status.aidl",
+    ],
+    backend:
+    {
+        cpp: {
+            enabled: true,
+        },
+        java: {
+            // Already generated as part of the entire media java library.
+            enabled: false,
+        },
+        ndk: {
+            // Not currently needed, and disabled because of b/146172425
+            enabled: false,
+        },
+    },
+    imports: [ "audio_common-aidl" ],
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index df799fd..33ec46a 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -903,6 +903,18 @@
         }
     }
 
+    /**
+     * Returns a human readable name for a given device type
+     * @param device a native device type, NOT an AudioDeviceInfo type
+     * @return a string describing the device type
+     */
+    public static @NonNull String getDeviceName(int device) {
+        if ((device & DEVICE_BIT_IN) != 0) {
+            return getInputDeviceName(device);
+        }
+        return getOutputDeviceName(device);
+    }
+
     // phone state, match audio_mode???
     public static final int PHONE_STATE_OFFCALL = 0;
     public static final int PHONE_STATE_RINGING = 1;
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
new file mode 100644
index 0000000..88a8295
--- /dev/null
+++ b/media/java/android/media/MediaMetrics.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Bundle;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * MediaMetrics is the Java interface to the MediaMetrics service.
+ *
+ * This is used to collect media statistics by the framework.
+ * It is not intended for direct application use.
+ *
+ * @hide
+ */
+public class MediaMetrics {
+    public static final String TAG = "MediaMetrics";
+
+    /**
+     * The TYPE constants below should match those in native MediaMetricsItem.h
+     */
+    private static final int TYPE_NONE = 0;
+    private static final int TYPE_INT32 = 1;     // Java integer
+    private static final int TYPE_INT64 = 2;     // Java long
+    private static final int TYPE_DOUBLE = 3;    // Java double
+    private static final int TYPE_CSTRING = 4;   // Java string
+    private static final int TYPE_RATE = 5;      // Two longs, ignored in Java
+
+    // The charset used for encoding Strings to bytes.
+    private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8;
+
+    /**
+     * Item records properties and delivers to the MediaMetrics service
+     *
+     */
+    public static class Item {
+
+        /*
+         * MediaMetrics Item
+         *
+         * Creates a Byte String and sends to the MediaMetrics service.
+         * The Byte String serves as a compact form for logging data
+         * with low overhead for storage.
+         *
+         * The Byte String format is as follows:
+         *
+         * For Java
+         *  int64 corresponds to long
+         *  int32, uint32 corresponds to int
+         *  uint16 corresponds to char
+         *  uint8, int8 corresponds to byte
+         *
+         * For items transmitted from Java, uint8 and uint32 values are limited
+         * to INT8_MAX and INT32_MAX.  This constrains the size of large items
+         * to 2GB, which is consistent with ByteBuffer max size. A native item
+         * can conceivably have size of 4GB.
+         *
+         * Physical layout of integers and doubles within the MediaMetrics byte string
+         * is in Native / host order, which is usually little endian.
+         *
+         * Note that primitive data (ints, doubles) within a Byte String has
+         * no extra padding or alignment requirements, like ByteBuffer.
+         *
+         * -- begin of item
+         * -- begin of header
+         * (uint32) item size: including the item size field
+         * (uint32) header size, including the item size and header size fields.
+         * (uint16) version: exactly 0
+         * (uint16) key size, that is key strlen + 1 for zero termination.
+         * (int8)+ key, a string which is 0 terminated (UTF-8).
+         * (int32) pid
+         * (int32) uid
+         * (int64) timestamp
+         * -- end of header
+         * -- begin body
+         * (uint32) number of properties
+         * -- repeat for number of properties
+         *     (uint16) property size, including property size field itself
+         *     (uint8) type of property
+         *     (int8)+ key string, including 0 termination
+         *      based on type of property (given above), one of:
+         *       (int32)
+         *       (int64)
+         *       (double)
+         *       (int8)+ for TYPE_CSTRING, including 0 termination
+         *       (int64, int64) for rate
+         * -- end body
+         * -- end of item
+         *
+         * To record a MediaMetrics event, one creates a new item with an id,
+         * then use a series of puts to add properties
+         * and then a record() to send to the MediaMetrics service.
+         *
+         * The properties may not be unique, and putting a later property with
+         * the same name as an earlier property will overwrite the value and type
+         * of the prior property.
+         *
+         * The timestamp can only be recorded by a system service (and is ignored otherwise;
+         * the MediaMetrics service will fill in the timestamp as needed).
+         *
+         * The units of time are in SystemClock.elapsedRealtimeNanos().
+         *
+         * A clear() may be called to reset the properties to empty, the time to 0, but keep
+         * the other entries the same. This may be called after record().
+         * Additional properties may be added after calling record().  Changing the same property
+         * repeatedly is discouraged as - for this particular implementation - extra data
+         * is stored per change.
+         *
+         * new MediaMetrics.Item(mSomeId)
+         *     .putString("event", "javaCreate")
+         *     .putInt("value", intValue)
+         *     .record();
+         */
+
+        /**
+         * Creates an Item with server added uid, time.
+         *
+         * This is the typical way to record a MediaMetrics item.
+         *
+         * @param key the Metrics ID associated with the item.
+         */
+        public Item(String key) {
+            this(key, -1 /* pid */, -1 /* uid */, 0 /* SystemClock.elapsedRealtimeNanos() */,
+                    2048 /* capacity */);
+        }
+
+        /**
+         * Creates an Item specifying pid, uid, time, and initial Item capacity.
+         *
+         * This might be used by a service to specify a different PID or UID for a client.
+         *
+         * @param key the Metrics ID associated with the item.
+         *        An app may only set properties on an item which has already been
+         *        logged previously by a service.
+         * @param pid the process ID corresponding to the item.
+         *        A value of -1 (or a record() from an app instead of a service) causes
+         *        the MediaMetrics service to fill this in.
+         * @param uid the user ID corresponding to the item.
+         *        A value of -1 (or a record() from an app instead of a service) causes
+         *        the MediaMetrics service to fill this in.
+         * @param timeNs the time when the item occurred (may be in the past).
+         *        A value of 0 (or a record() from an app instead of a service) causes
+         *        the MediaMetrics service to fill it in.
+         *        Should be obtained from SystemClock.elapsedRealtimeNanos().
+         * @param capacity the anticipated size to use for the buffer.
+         *        If the capacity is too small, the buffer will be resized to accommodate.
+         *        This is amortized to copy data no more than twice.
+         */
+        public Item(String key, int pid, int uid, long timeNs, int capacity) {
+            final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+            final int keyLength = keyBytes.length;
+            if (keyLength > Character.MAX_VALUE - 1) {
+                throw new IllegalArgumentException("Key length too large");
+            }
+
+            // Version 0 - compute the header offsets here.
+            mHeaderSize = 4 + 4 + 2 + 2 + keyLength + 1 + 4 + 4 + 8; // see format above.
+            mPidOffset = mHeaderSize - 16;
+            mUidOffset = mHeaderSize - 12;
+            mTimeNsOffset = mHeaderSize - 8;
+            mPropertyCountOffset = mHeaderSize;
+            mPropertyStartOffset = mHeaderSize + 4;
+
+            mKey = key;
+            mBuffer = ByteBuffer.allocateDirect(
+                    Math.max(capacity, mHeaderSize + MINIMUM_PAYLOAD_SIZE));
+
+            // Version 0 - fill the ByteBuffer with the header (some details updated later).
+            mBuffer.order(ByteOrder.nativeOrder())
+                .putInt((int) 0)                      // total size in bytes (filled in later)
+                .putInt((int) mHeaderSize)            // size of header
+                .putChar((char) FORMAT_VERSION)       // version
+                .putChar((char) (keyLength + 1))      // length, with zero termination
+                .put(keyBytes).put((byte) 0)
+                .putInt(pid)
+                .putInt(uid)
+                .putLong(timeNs);
+            if (mHeaderSize != mBuffer.position()) {
+                throw new IllegalStateException("Mismatched sizing");
+            }
+            mBuffer.putInt(0);     // number of properties (to be later filled in by record()).
+        }
+
+        /**
+         * Sets the property with key to an integer (32 bit) value.
+         *
+         * @param key
+         * @param value
+         * @return itself
+         */
+        public Item putInt(String key, int value) {
+            final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+            final char propSize = (char) reserveProperty(keyBytes, 4 /* payloadSize */);
+            final int estimatedFinalPosition = mBuffer.position() + propSize;
+            mBuffer.putChar(propSize)
+                .put((byte) TYPE_INT32)
+                .put(keyBytes).put((byte) 0) // key, zero terminated
+                .putInt(value);
+            ++mPropertyCount;
+            if (mBuffer.position() != estimatedFinalPosition) {
+                throw new IllegalStateException("Final position " + mBuffer.position()
+                        + " != estimatedFinalPosition " + estimatedFinalPosition);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the property with key to a long (64 bit) value.
+         *
+         * @param key
+         * @param value
+         * @return itself
+         */
+        public Item putLong(String key, long value) {
+            final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+            final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+            final int estimatedFinalPosition = mBuffer.position() + propSize;
+            mBuffer.putChar(propSize)
+                .put((byte) TYPE_INT64)
+                .put(keyBytes).put((byte) 0) // key, zero terminated
+                .putLong(value);
+            ++mPropertyCount;
+            if (mBuffer.position() != estimatedFinalPosition) {
+                throw new IllegalStateException("Final position " + mBuffer.position()
+                    + " != estimatedFinalPosition " + estimatedFinalPosition);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the property with key to a double value.
+         *
+         * @param key
+         * @param value
+         * @return itself
+         */
+        public Item putDouble(String key, double value) {
+            final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+            final char propSize = (char) reserveProperty(keyBytes, 8 /* payloadSize */);
+            final int estimatedFinalPosition = mBuffer.position() + propSize;
+            mBuffer.putChar(propSize)
+                .put((byte) TYPE_DOUBLE)
+                .put(keyBytes).put((byte) 0) // key, zero terminated
+                .putDouble(value);
+            ++mPropertyCount;
+            if (mBuffer.position() != estimatedFinalPosition) {
+                throw new IllegalStateException("Final position " + mBuffer.position()
+                    + " != estimatedFinalPosition " + estimatedFinalPosition);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the property with key to a String value.
+         *
+         * @param key
+         * @param value
+         * @return itself
+         */
+        public Item putString(String key, String value) {
+            final byte[] keyBytes = key.getBytes(MEDIAMETRICS_CHARSET);
+            final byte[] valueBytes = value.getBytes(MEDIAMETRICS_CHARSET);
+            final char propSize = (char) reserveProperty(keyBytes, valueBytes.length + 1);
+            final int estimatedFinalPosition = mBuffer.position() + propSize;
+            mBuffer.putChar(propSize)
+                .put((byte) TYPE_CSTRING)
+                .put(keyBytes).put((byte) 0) // key, zero terminated
+                .put(valueBytes).put((byte) 0); // value, zero term.
+            ++mPropertyCount;
+            if (mBuffer.position() != estimatedFinalPosition) {
+                throw new IllegalStateException("Final position " + mBuffer.position()
+                    + " != estimatedFinalPosition " + estimatedFinalPosition);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the pid to the provided value.
+         *
+         * @param pid which can be -1 if the service is to fill it in from the calling info.
+         * @return itself
+         */
+        public Item setPid(int pid) {
+            mBuffer.putInt(mPidOffset, pid); // pid location in byte string.
+            return this;
+        }
+
+        /**
+         * Sets the uid to the provided value.
+         *
+         * The UID represents the client associated with the property. This must be the UID
+         * of the application if it comes from the application client.
+         *
+         * Trusted services are allowed to set the uid for a client-related item.
+         *
+         * @param uid which can be -1 if the service is to fill it in from calling info.
+         * @return itself
+         */
+        public Item setUid(int uid) {
+            mBuffer.putInt(mUidOffset, uid); // uid location in byte string.
+            return this;
+        }
+
+        /**
+         * Sets the timestamp to the provided value.
+         *
+         * The time is referenced by the Boottime obtained by SystemClock.elapsedRealtimeNanos().
+         * This should be associated with the occurrence of the event.  It is recommended that
+         * the event be registered immediately when it occurs, and no later than 500ms
+         * (and certainly not in the future).
+         *
+         * @param timeNs which can be 0 if the service is to fill it in at the time of call.
+         * @return itself
+         */
+        public Item setTimestamp(long timeNs) {
+            mBuffer.putLong(mTimeNsOffset, timeNs); // time location in byte string.
+            return this;
+        }
+
+        /**
+         * Clears the properties and resets the time to 0.
+         *
+         * No other values are changed.
+         *
+         * @return itself
+         */
+        public Item clear() {
+            mBuffer.position(mPropertyStartOffset);
+            mBuffer.limit(mBuffer.capacity());
+            mBuffer.putLong(mTimeNsOffset, 0); // reset time.
+            mPropertyCount = 0;
+            return this;
+        }
+
+        /**
+         * Sends the item to the MediaMetrics service.
+         *
+         * The item properties are unchanged, hence record() may be called more than once
+         * to send the same item twice. Also, record() may be called without any properties.
+         *
+         * @return true if successful.
+         */
+        public boolean record() {
+            updateHeader();
+            return native_submit_bytebuffer(mBuffer, mBuffer.limit()) >= 0;
+        }
+
+        /**
+         * Converts the Item to a Bundle.
+         *
+         * This is primarily used as a test API for CTS.
+         *
+         * @return a Bundle with the keys set according to data in the Item's buffer.
+         */
+        @TestApi
+        public Bundle toBundle() {
+            updateHeader();
+
+            final ByteBuffer buffer = mBuffer.duplicate();
+            buffer.order(ByteOrder.nativeOrder()) // restore order property
+                .flip();                          // convert from write buffer to read buffer
+
+            return toBundle(buffer);
+        }
+
+        // The following constants are used for tests to extract
+        // the content of the Bundle for CTS testing.
+        @TestApi
+        public static final String BUNDLE_TOTAL_SIZE = "_totalSize";
+        @TestApi
+        public static final String BUNDLE_HEADER_SIZE = "_headerSize";
+        @TestApi
+        public static final String BUNDLE_VERSION = "_version";
+        @TestApi
+        public static final String BUNDLE_KEY_SIZE = "_keySize";
+        @TestApi
+        public static final String BUNDLE_KEY = "_key";
+        @TestApi
+        public static final String BUNDLE_PID = "_pid";
+        @TestApi
+        public static final String BUNDLE_UID = "_uid";
+        @TestApi
+        public static final String BUNDLE_TIMESTAMP = "_timestamp";
+        @TestApi
+        public static final String BUNDLE_PROPERTY_COUNT = "_propertyCount";
+
+        /**
+         * Converts a buffer contents to a bundle
+         *
+         * This is primarily used as a test API for CTS.
+         *
+         * @param buffer contains the byte data serialized according to the byte string version.
+         * @return a Bundle with the keys set according to data in the buffer.
+         */
+        @TestApi
+        public static Bundle toBundle(ByteBuffer buffer) {
+            final Bundle bundle = new Bundle();
+
+            final int totalSize = buffer.getInt();
+            final int headerSize = buffer.getInt();
+            final char version = buffer.getChar();
+            final char keySize = buffer.getChar(); // includes zero termination, i.e. keyLength + 1
+
+            if (totalSize < 0 || headerSize < 0) {
+                throw new IllegalArgumentException("Item size cannot be > " + Integer.MAX_VALUE);
+            }
+            final String key;
+            if (keySize > 0) {
+                key = getStringFromBuffer(buffer, keySize);
+            } else {
+                throw new IllegalArgumentException("Illegal null key");
+            }
+
+            final int pid = buffer.getInt();
+            final int uid = buffer.getInt();
+            final long timestamp = buffer.getLong();
+
+            // Verify header size (depending on version).
+            final int headerRead = buffer.position();
+            if (version == 0) {
+                if (headerRead != headerSize) {
+                    throw new IllegalArgumentException(
+                            "Item key:" + key
+                            + " headerRead:" + headerRead + " != headerSize:" + headerSize);
+                }
+            } else {
+                // future versions should only increase header size
+                // by adding to the end.
+                if (headerRead > headerSize) {
+                    throw new IllegalArgumentException(
+                            "Item key:" + key
+                            + " headerRead:" + headerRead + " > headerSize:" + headerSize);
+                } else if (headerRead < headerSize) {
+                    buffer.position(headerSize);
+                }
+            }
+
+            // Body always starts with properties.
+            final int propertyCount = buffer.getInt();
+            if (propertyCount < 0) {
+                throw new IllegalArgumentException(
+                        "Cannot have more than " + Integer.MAX_VALUE + " properties");
+            }
+            bundle.putInt(BUNDLE_TOTAL_SIZE, totalSize);
+            bundle.putInt(BUNDLE_HEADER_SIZE, headerSize);
+            bundle.putChar(BUNDLE_VERSION, version);
+            bundle.putChar(BUNDLE_KEY_SIZE, keySize);
+            bundle.putString(BUNDLE_KEY, key);
+            bundle.putInt(BUNDLE_PID, pid);
+            bundle.putInt(BUNDLE_UID, uid);
+            bundle.putLong(BUNDLE_TIMESTAMP, timestamp);
+            bundle.putInt(BUNDLE_PROPERTY_COUNT, propertyCount);
+
+            for (int i = 0; i < propertyCount; ++i) {
+                final int initialBufferPosition = buffer.position();
+                final char propSize = buffer.getChar();
+                final byte type = buffer.get();
+
+                // Log.d(TAG, "(" + i + ") propSize:" + ((int)propSize) + " type:" + type);
+                final String propKey = getStringFromBuffer(buffer);
+                switch (type) {
+                    case TYPE_INT32:
+                        bundle.putInt(propKey, buffer.getInt());
+                        break;
+                    case TYPE_INT64:
+                        bundle.putLong(propKey, buffer.getLong());
+                        break;
+                    case TYPE_DOUBLE:
+                        bundle.putDouble(propKey, buffer.getDouble());
+                        break;
+                    case TYPE_CSTRING:
+                        bundle.putString(propKey, getStringFromBuffer(buffer));
+                        break;
+                    case TYPE_NONE:
+                        break; // ignore on Java side
+                    case TYPE_RATE:
+                        buffer.getLong();  // consume the first int64_t of rate
+                        buffer.getLong();  // consume the second int64_t of rate
+                        break; // ignore on Java side
+                    default:
+                        // These are unsupported types for version 0
+                        // We ignore them if the version is greater than 0.
+                        if (version == 0) {
+                            throw new IllegalArgumentException(
+                                    "Property " + propKey + " has unsupported type " + type);
+                        }
+                        buffer.position(initialBufferPosition + propSize); // advance and skip
+                        break;
+                }
+                final int deltaPosition = buffer.position() - initialBufferPosition;
+                if (deltaPosition != propSize) {
+                    throw new IllegalArgumentException("propSize:" + propSize
+                        + " != deltaPosition:" + deltaPosition);
+                }
+            }
+
+            final int finalPosition = buffer.position();
+            if (finalPosition != totalSize) {
+                throw new IllegalArgumentException("totalSize:" + totalSize
+                    + " != finalPosition:" + finalPosition);
+            }
+            return bundle;
+        }
+
+        // Version 0 byte offsets for the header.
+        private static final int FORMAT_VERSION = 0;
+        private static final int TOTAL_SIZE_OFFSET = 0;
+        private static final int HEADER_SIZE_OFFSET = 4;
+        private static final int MINIMUM_PAYLOAD_SIZE = 4;
+        private final int mPidOffset;            // computed in constructor
+        private final int mUidOffset;            // computed in constructor
+        private final int mTimeNsOffset;         // computed in constructor
+        private final int mPropertyCountOffset;  // computed in constructor
+        private final int mPropertyStartOffset;  // computed in constructor
+        private final int mHeaderSize;           // computed in constructor
+
+        private final String mKey;
+
+        private ByteBuffer mBuffer;     // may be reallocated if capacity is insufficient.
+        private int mPropertyCount = 0; // overflow not checked (mBuffer would overflow first).
+
+        private int reserveProperty(byte[] keyBytes, int payloadSize) {
+            final int keyLength = keyBytes.length;
+            if (keyLength > Character.MAX_VALUE) {
+                throw new IllegalStateException("property key too long "
+                        + new String(keyBytes, MEDIAMETRICS_CHARSET));
+            }
+            if (payloadSize > Character.MAX_VALUE) {
+                throw new IllegalStateException("payload too large " + payloadSize);
+            }
+
+            // See the byte string property format above.
+            final int size = 2      /* length */
+                    + 1             /* type */
+                    + keyLength + 1 /* key length with zero termination */
+                    + payloadSize;  /* payload size */
+
+            if (size > Character.MAX_VALUE) {
+                throw new IllegalStateException("Item property "
+                        + new String(keyBytes, MEDIAMETRICS_CHARSET) + " is too large to send");
+            }
+
+            if (mBuffer.remaining() < size) {
+                int newCapacity = mBuffer.position() + size;
+                if (newCapacity > Integer.MAX_VALUE >> 1) {
+                    throw new IllegalStateException(
+                        "Item memory requirements too large: " + newCapacity);
+                }
+                newCapacity <<= 1;
+                ByteBuffer buffer = ByteBuffer.allocateDirect(newCapacity);
+                buffer.order(ByteOrder.nativeOrder());
+
+                // Copy data from old buffer to new buffer.
+                mBuffer.flip();
+                buffer.put(mBuffer);
+
+                // set buffer to new buffer
+                mBuffer = buffer;
+            }
+            return size;
+        }
+
+        // Used for test
+        private static String getStringFromBuffer(ByteBuffer buffer) {
+            return getStringFromBuffer(buffer, Integer.MAX_VALUE);
+        }
+
+        // Used for test
+        private static String getStringFromBuffer(ByteBuffer buffer, int size) {
+            int i = buffer.position();
+            int limit = buffer.limit();
+            if (size < Integer.MAX_VALUE - i && i + size < limit) {
+                limit = i + size;
+            }
+            for (; i < limit; ++i) {
+                if (buffer.get(i) == 0) {
+                    final int newPosition = i + 1;
+                    if (size != Integer.MAX_VALUE && newPosition - buffer.position() != size) {
+                        throw new IllegalArgumentException("chars consumed at " + i + ": "
+                            + (newPosition - buffer.position()) + " != size: " + size);
+                    }
+                    final String found;
+                    if (buffer.hasArray()) {
+                        found = new String(
+                            buffer.array(), buffer.position() + buffer.arrayOffset(),
+                            i - buffer.position(), MEDIAMETRICS_CHARSET);
+                        buffer.position(newPosition);
+                    } else {
+                        final byte[] array = new byte[i - buffer.position()];
+                        buffer.get(array);
+                        found = new String(array, MEDIAMETRICS_CHARSET);
+                        buffer.get(); // remove 0.
+                    }
+                    return found;
+                }
+            }
+            throw new IllegalArgumentException(
+                    "No zero termination found in string position: "
+                    + buffer.position() + " end: " + i);
+        }
+
+        /**
+         * May be called multiple times - just makes the header consistent with the current
+         * properties written.
+         */
+        private void updateHeader() {
+            // Buffer sized properly in constructor.
+            mBuffer.putInt(TOTAL_SIZE_OFFSET, mBuffer.position())      // set total length
+                .putInt(mPropertyCountOffset, (char) mPropertyCount); // set number of properties
+        }
+    }
+
+    private static native int native_submit_bytebuffer(@NonNull ByteBuffer buffer, int length);
+}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3e6f4c0..bad0ef4 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -23,6 +23,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -51,7 +52,6 @@
  * @hide
  */
 public class MediaRouter2 {
-
     /** @hide */
     @Retention(SOURCE)
     @IntDef(value = {
@@ -102,13 +102,11 @@
             new CopyOnWriteArrayList<>();
 
     private final String mPackageName;
+    @GuardedBy("sLock")
     private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
 
-    //TODO: Use a lock for this to cover the below use case
-    // mRouter.setControlCategories(...);
-    // routes = mRouter.getRoutes();
-    // The current implementation returns empty list
-    private volatile List<String> mControlCategories = Collections.emptyList();
+    @GuardedBy("sLock")
+    private List<String> mControlCategories = Collections.emptyList();
 
     private MediaRoute2Info mSelectedRoute;
     @GuardedBy("sLock")
@@ -117,7 +115,9 @@
     private Client2 mClient;
 
     final Handler mHandler;
-    volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
+    @GuardedBy("sLock")
+    private boolean mShouldUpdateRoutes;
+    private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
 
     /**
      * Gets an instance of the media router associated with the context.
@@ -171,8 +171,7 @@
     /**
      * Registers a callback to discover routes and to receive events when they change.
      * <p>
-     * If you register the same callback twice or more, the previous arguments will be overwritten
-     * with the new arguments.
+     * If you register the same callback twice or more, it will be ignored.
      * </p>
      */
     public void registerCallback(@NonNull @CallbackExecutor Executor executor,
@@ -180,18 +179,10 @@
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(callback, "callback must not be null");
 
-        CallbackRecord record;
-        // This is required to prevent adding the same callback twice.
-        synchronized (mCallbackRecords) {
-            final int index = findCallbackRecordIndexLocked(callback);
-            if (index < 0) {
-                record = new CallbackRecord(callback);
-                mCallbackRecords.add(record);
-            } else {
-                record = mCallbackRecords.get(index);
-            }
-            record.mExecutor = executor;
-            record.mFlags = flags;
+        CallbackRecord record = new CallbackRecord(callback, executor, flags);
+        if (!mCallbackRecords.addIfAbsent(record)) {
+            Log.w(TAG, "Ignoring the same callback");
+            return;
         }
 
         synchronized (sLock) {
@@ -206,8 +197,6 @@
                 }
             }
         }
-        //TODO: Is it thread-safe?
-        record.notifyRoutes();
 
         //TODO: Update discovery request here.
     }
@@ -222,23 +211,20 @@
     public void unregisterCallback(@NonNull Callback callback) {
         Objects.requireNonNull(callback, "callback must not be null");
 
-        synchronized (mCallbackRecords) {
-            final int index = findCallbackRecordIndexLocked(callback);
-            if (index < 0) {
-                Log.w(TAG, "Ignoring to remove unknown callback. " + callback);
-                return;
-            }
-            mCallbackRecords.remove(index);
-            synchronized (sLock) {
-                if (mCallbackRecords.size() == 0 && mClient != null) {
-                    try {
-                        mMediaRouterService.unregisterClient2(mClient);
-                    } catch (RemoteException ex) {
-                        Log.e(TAG, "Unable to unregister media router.", ex);
-                    }
-                    //TODO: Clean up mRoutes. (onHandler?)
-                    mClient = null;
+        if (!mCallbackRecords.remove(new CallbackRecord(callback, null, 0))) {
+            Log.w(TAG, "Ignoring unknown callback");
+            return;
+        }
+
+        synchronized (sLock) {
+            if (mCallbackRecords.size() == 0 && mClient != null) {
+                try {
+                    mMediaRouterService.unregisterClient2(mClient);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to unregister media router.", ex);
                 }
+                //TODO: Clean up mRoutes. (onHandler?)
+                mClient = null;
             }
         }
     }
@@ -246,26 +232,52 @@
     //TODO(b/139033746): Rename "Control Category" when it's finalized.
     /**
      * Sets the control categories of the application.
-     * Routes that support at least one of the given control categories only exists and are handled
+     * Routes that support at least one of the given control categories are handled
      * by the media router.
      */
     public void setControlCategories(@NonNull Collection<String> controlCategories) {
         Objects.requireNonNull(controlCategories, "control categories must not be null");
 
-        // To ensure invoking callbacks correctly according to control categories
-        mHandler.sendMessage(obtainMessage(MediaRouter2::setControlCategoriesOnHandler,
-                MediaRouter2.this, new ArrayList<>(controlCategories)));
+        List<String> newControlCategories = new ArrayList<>(controlCategories);
+
+        synchronized (sLock) {
+            mShouldUpdateRoutes = true;
+
+            // invoke callbacks due to control categories change
+            handleControlCategoriesChangedLocked(newControlCategories);
+            if (mClient != null) {
+                try {
+                    mMediaRouterService.setControlCategories(mClient, mControlCategories);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to set control categories.", ex);
+                }
+            }
+        }
     }
 
     /**
      * Gets the unmodifiable list of {@link MediaRoute2Info routes} currently
      * known to the media router.
+     * Please note that the list can be changed before callbacks are invoked.
      *
      * @return the list of routes that support at least one of the control categories set by
      * the application
      */
     @NonNull
     public List<MediaRoute2Info> getRoutes() {
+        synchronized (sLock) {
+            if (mShouldUpdateRoutes) {
+                mShouldUpdateRoutes = false;
+
+                List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
+                for (MediaRoute2Info route : mRoutes.values()) {
+                    if (route.supportsControlCategory(mControlCategories)) {
+                        filteredRoutes.add(route);
+                    }
+                }
+                mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
+            }
+        }
         return mFilteredRoutes;
     }
 
@@ -379,43 +391,16 @@
         }
     }
 
-    @GuardedBy("mCallbackRecords")
-    private int findCallbackRecordIndexLocked(Callback callback) {
-        final int count = mCallbackRecords.size();
-        for (int i = 0; i < count; i++) {
-            CallbackRecord callbackRecord = mCallbackRecords.get(i);
-            if (callbackRecord.mCallback == callback) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    private void setControlCategoriesOnHandler(List<String> newControlCategories) {
-        List<String> prevControlCategories = mControlCategories;
+    private void handleControlCategoriesChangedLocked(List<String> newControlCategories) {
         List<MediaRoute2Info> addedRoutes = new ArrayList<>();
         List<MediaRoute2Info> removedRoutes = new ArrayList<>();
-        List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
 
+        List<String> prevControlCategories = mControlCategories;
         mControlCategories = newControlCategories;
-        Client2 client;
-        synchronized (sLock) {
-            client = mClient;
-        }
-        if (client != null) {
-            try {
-                mMediaRouterService.setControlCategories(client, mControlCategories);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Unable to set control categories.", ex);
-            }
-        }
 
         for (MediaRoute2Info route : mRoutes.values()) {
             boolean preSupported = route.supportsControlCategory(prevControlCategories);
             boolean postSupported = route.supportsControlCategory(newControlCategories);
-            if (postSupported) {
-                filteredRoutes.add(route);
-            }
             if (preSupported == postSupported) {
                 continue;
             }
@@ -425,13 +410,14 @@
                 addedRoutes.add(route);
             }
         }
-        mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
 
         if (removedRoutes.size() > 0) {
-            notifyRoutesRemoved(removedRoutes);
+            mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesRemoved,
+                    MediaRouter2.this, removedRoutes));
         }
         if (addedRoutes.size() > 0) {
-            notifyRoutesAdded(addedRoutes);
+            mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesAdded,
+                    MediaRouter2.this, addedRoutes));
         }
     }
 
@@ -441,42 +427,47 @@
         //  2) Call onRouteSelected(system_route, reason_fallback) if previously selected route
         //     does not exist anymore. => We may need 'boolean MediaRoute2Info#isSystemRoute()'.
         List<MediaRoute2Info> addedRoutes = new ArrayList<>();
-        for (MediaRoute2Info route : routes) {
-            mRoutes.put(route.getUniqueId(), route);
-            if (route.supportsControlCategory(mControlCategories)) {
-                addedRoutes.add(route);
+        synchronized (sLock) {
+            for (MediaRoute2Info route : routes) {
+                mRoutes.put(route.getUniqueId(), route);
+                if (route.supportsControlCategory(mControlCategories)) {
+                    addedRoutes.add(route);
+                }
             }
+            mShouldUpdateRoutes = true;
         }
         if (addedRoutes.size() > 0) {
-            refreshFilteredRoutes();
             notifyRoutesAdded(addedRoutes);
         }
     }
 
     void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
         List<MediaRoute2Info> removedRoutes = new ArrayList<>();
-        for (MediaRoute2Info route : routes) {
-            mRoutes.remove(route.getUniqueId());
-            if (route.supportsControlCategory(mControlCategories)) {
-                removedRoutes.add(route);
+        synchronized (sLock) {
+            for (MediaRoute2Info route : routes) {
+                mRoutes.remove(route.getUniqueId());
+                if (route.supportsControlCategory(mControlCategories)) {
+                    removedRoutes.add(route);
+                }
             }
+            mShouldUpdateRoutes = true;
         }
         if (removedRoutes.size() > 0) {
-            refreshFilteredRoutes();
             notifyRoutesRemoved(removedRoutes);
         }
     }
 
     void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
         List<MediaRoute2Info> changedRoutes = new ArrayList<>();
-        for (MediaRoute2Info route : routes) {
-            mRoutes.put(route.getUniqueId(), route);
-            if (route.supportsControlCategory(mControlCategories)) {
-                changedRoutes.add(route);
+        synchronized (sLock) {
+            for (MediaRoute2Info route : routes) {
+                mRoutes.put(route.getUniqueId(), route);
+                if (route.supportsControlCategory(mControlCategories)) {
+                    changedRoutes.add(route);
+                }
             }
         }
         if (changedRoutes.size() > 0) {
-            refreshFilteredRoutes();
             notifyRoutesChanged(changedRoutes);
         }
     }
@@ -500,17 +491,6 @@
         notifyRouteSelected(route, reason, controlHints);
     }
 
-    private void refreshFilteredRoutes() {
-        List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
-
-        for (MediaRoute2Info route : mRoutes.values()) {
-            if (route.supportsControlCategory(mControlCategories)) {
-                filteredRoutes.add(route);
-            }
-        }
-        mFilteredRoutes = Collections.unmodifiableList(filteredRoutes);
-    }
-
     private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
         for (CallbackRecord record: mCallbackRecords) {
             record.mExecutor.execute(
@@ -544,13 +524,16 @@
      */
     public static class Callback {
         /**
-         * Called when routes are added.
+         * Called when routes are added. Whenever you registers a callback, this will
+         * be invoked with known routes.
+         *
          * @param routes the list of routes that have been added. It's never empty.
          */
         public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
          * Called when routes are removed.
+         *
          * @param routes the list of routes that have been removed. It's never empty.
          */
         public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
@@ -569,6 +552,7 @@
 
         /**
          * Called when a route is selected. Exactly one route can be selected at a time.
+         *
          * @param route the selected route.
          * @param reason the reason why the route is selected.
          * @param controlHints An optional bundle of provider-specific arguments which may be
@@ -587,16 +571,26 @@
         public Executor mExecutor;
         public int mFlags;
 
-        CallbackRecord(@NonNull Callback callback) {
+        CallbackRecord(@NonNull Callback callback, @Nullable Executor executor, int flags) {
             mCallback = callback;
+            mExecutor = executor;
+            mFlags = flags;
         }
 
-        void notifyRoutes() {
-            final List<MediaRoute2Info> routes = mFilteredRoutes;
-            // notify only when bound to media router service.
-            if (routes.size() > 0) {
-                mExecutor.execute(() -> mCallback.onRoutesAdded(routes));
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
             }
+            if (!(obj instanceof CallbackRecord)) {
+                return false;
+            }
+            return mCallback == ((CallbackRecord) obj).mCallback;
+        }
+
+        @Override
+        public int hashCode() {
+            return mCallback.hashCode();
         }
     }
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index d56dd11..502538d 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -57,7 +57,7 @@
     private Client mClient;
     private final IMediaRouterService mMediaRouterService;
     final Handler mHandler;
-    final List<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
+    final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>();
 
     private final Object mRoutesLock = new Object();
     @GuardedBy("mRoutesLock")
@@ -99,14 +99,10 @@
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(callback, "callback must not be null");
 
-        CallbackRecord callbackRecord;
-        synchronized (mCallbackRecords) {
-            if (findCallbackRecordIndexLocked(callback) >= 0) {
-                Log.w(TAG, "Ignoring to add the same callback twice.");
-                return;
-            }
-            callbackRecord = new CallbackRecord(executor, callback);
-            mCallbackRecords.add(callbackRecord);
+        CallbackRecord callbackRecord = new CallbackRecord(executor, callback);
+        if (!mCallbackRecords.addIfAbsent(callbackRecord)) {
+            Log.w(TAG, "Ignoring to add the same callback twice.");
+            return;
         }
 
         synchronized (sLock) {
@@ -118,8 +114,6 @@
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to register media router manager.", ex);
                 }
-            } else {
-                callbackRecord.notifyRoutes();
             }
         }
     }
@@ -132,36 +126,23 @@
     public void unregisterCallback(@NonNull Callback callback) {
         Objects.requireNonNull(callback, "callback must not be null");
 
-        synchronized (mCallbackRecords) {
-            final int index = findCallbackRecordIndexLocked(callback);
-            if (index < 0) {
-                Log.w(TAG, "Ignore removing unknown callback. " + callback);
-                return;
-            }
-            mCallbackRecords.remove(index);
-            synchronized (sLock) {
-                if (mCallbackRecords.size() == 0 && mClient != null) {
-                    try {
-                        mMediaRouterService.unregisterManager(mClient);
-                    } catch (RemoteException ex) {
-                        Log.e(TAG, "Unable to unregister media router manager", ex);
-                    }
-                    //TODO: clear mRoutes?
-                    mClient = null;
-                }
-            }
+        if (!mCallbackRecords.remove(new CallbackRecord(null, callback))) {
+            Log.w(TAG, "Ignore removing unknown callback. " + callback);
+            return;
         }
-    }
 
-    @GuardedBy("mCallbackRecords")
-    private int findCallbackRecordIndexLocked(Callback callback) {
-        final int count = mCallbackRecords.size();
-        for (int i = 0; i < count; i++) {
-            if (mCallbackRecords.get(i).mCallback == callback) {
-                return i;
+        synchronized (sLock) {
+            if (mCallbackRecords.size() == 0 && mClient != null) {
+                try {
+                    mMediaRouterService.unregisterManager(mClient);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "Unable to unregister media router manager", ex);
+                }
+                //TODO: clear mRoutes?
+                mClient = null;
+                mControlCategoryMap.clear();
             }
         }
-        return -1;
     }
 
     //TODO: Use cache not to create array. For now, it's unclear when to purge the cache.
@@ -187,7 +168,6 @@
                 }
             }
         }
-        //TODO: Should we cache this?
         return routes;
     }
 
@@ -342,10 +322,14 @@
     }
 
     void updateControlCategories(String packageName, List<String> categories) {
-        mControlCategoryMap.put(packageName, categories);
+        List<String> prevCategories = mControlCategoryMap.put(packageName, categories);
+        if ((prevCategories == null && categories.size() == 0)
+                || Objects.equals(categories, prevCategories)) {
+            return;
+        }
         for (CallbackRecord record : mCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mCallback.onControlCategoriesChanged(packageName));
+                    () -> record.mCallback.onControlCategoriesChanged(packageName, categories));
         }
     }
 
@@ -386,8 +370,10 @@
          * Called when the control categories of an app is changed.
          *
          * @param packageName the package name of the application
+         * @param controlCategories the list of control categories set by an application.
          */
-        public void onControlCategoriesChanged(@NonNull String packageName) {}
+        public void onControlCategoriesChanged(@NonNull String packageName,
+                @NonNull List<String> controlCategories) {}
     }
 
     final class CallbackRecord {
@@ -399,14 +385,20 @@
             mCallback = callback;
         }
 
-        void notifyRoutes() {
-            List<MediaRoute2Info> routes;
-            synchronized (mRoutesLock) {
-                routes = new ArrayList<>(mRoutes.values());
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
             }
-            if (routes.size() > 0) {
-                mExecutor.execute(() -> mCallback.onRoutesAdded(routes));
+            if (!(obj instanceof CallbackRecord)) {
+                return false;
             }
+            return mCallback ==  ((CallbackRecord) obj).mCallback;
+        }
+
+        @Override
+        public int hashCode() {
+            return mCallback.hashCode();
         }
     }
 
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 515f6a8..40e9073 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -19,6 +19,7 @@
 import android.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentProviderClient;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ServiceConnection;
 import android.net.Uri;
@@ -197,7 +198,7 @@
     private static Uri scanFileQuietly(ContentProviderClient client, File file) {
         Uri uri = null;
         try {
-            uri = MediaStore.scanFile(client, file.getCanonicalFile());
+            uri = MediaStore.scanFile(ContentResolver.wrap(client), file.getCanonicalFile());
             Log.d(TAG, "Scanned " + file + " to " + uri);
         } catch (Exception e) {
             Log.w(TAG, "Failed to scan " + file + ": " + e);
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 9064e68..a1e1591 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -908,7 +908,7 @@
         }
 
         // Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}.
-        return MediaStore.scanFile(mContext, outFile);
+        return MediaStore.scanFile(mContext.getContentResolver(), outFile);
     }
 
     private static final String getExternalDirectoryForType(final int type) {
diff --git a/media/java/android/media/RouteSessionController.java b/media/java/android/media/RouteSessionController.java
new file mode 100644
index 0000000..5ff7218
--- /dev/null
+++ b/media/java/android/media/RouteSessionController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * A class to control media route session in media route provider.
+ * For example, adding/removing/transferring routes to session can be done through this class.
+ * Instances are created by {@link MediaRouter2}.
+ *
+ * TODO: When session is introduced, change Javadoc of all methods/classes by using [@link Session].
+ *
+ * @hide
+ */
+public class RouteSessionController {
+    private final int mSessionId;
+    private final String mCategory;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
+            new CopyOnWriteArrayList<>();
+
+    private volatile boolean mIsReleased;
+
+    /**
+     * @param sessionId the ID of the session.
+     * @param category The category of media routes that the session includes.
+     */
+    RouteSessionController(int sessionId, @NonNull String category) {
+        mSessionId = sessionId;
+        mCategory = category;
+    }
+
+    /**
+     * @return the ID of this controller
+     */
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    /**
+     * @return the category of routes that the session includes.
+     */
+    @NonNull
+    public String getCategory() {
+        return mCategory;
+    }
+
+    /**
+     * @return the list of currently connected routes
+     */
+    @NonNull
+    public List<MediaRoute2Info> getRoutes() {
+        // TODO: Implement this when SessionInfo is introduced.
+        return null;
+    }
+
+    /**
+     * Returns true if the session is released, false otherwise.
+     * If it is released, then all other getters from this instance may return invalid values.
+     * Also, any operations to this instance will be ignored once released.
+     *
+     * @see #release
+     * @see Callback#onReleased
+     */
+    public boolean isReleased() {
+        return mIsReleased;
+    }
+
+    /**
+     * Add routes to the remote session.
+     *
+     * @see #getRoutes()
+     * @see Callback#onSessionInfoChanged
+     */
+    public void addRoutes(List<MediaRoute2Info> routes) {
+        // TODO: Implement this when the actual connection logic is implemented.
+    }
+
+    /**
+     * Remove routes from this session. Media may be stopped on those devices.
+     * Route removal requests that are not currently in {@link #getRoutes()} will be ignored.
+     *
+     * @see #getRoutes()
+     * @see Callback#onSessionInfoChanged
+     */
+    public void removeRoutes(List<MediaRoute2Info> routes) {
+        // TODO: Implement this when the actual connection logic is implemented.
+    }
+
+    /**
+     * Registers a {@link Callback} for monitoring route changes.
+     * If the same callback is registered previously, previous executor will be overwritten with the
+     * new one.
+     */
+    public void registerCallback(Executor executor, Callback callback) {
+        if (mIsReleased) {
+            return;
+        }
+        Objects.requireNonNull(executor, "executor must not be null");
+        Objects.requireNonNull(callback, "callback must not be null");
+
+        synchronized (mLock) {
+            CallbackRecord recordWithSameCallback = null;
+            for (CallbackRecord record : mCallbackRecords) {
+                if (callback == record.mCallback) {
+                    recordWithSameCallback = record;
+                    break;
+                }
+            }
+
+            if (recordWithSameCallback != null) {
+                recordWithSameCallback.mExecutor = executor;
+            } else {
+                mCallbackRecords.add(new CallbackRecord(executor, callback));
+            }
+        }
+    }
+
+    /**
+     * Unregisters a previously registered {@link Callback}.
+     */
+    public void unregisterCallback(Callback callback) {
+        Objects.requireNonNull(callback, "callback must not be null");
+
+        synchronized (mLock) {
+            CallbackRecord recordToRemove = null;
+            for (CallbackRecord record : mCallbackRecords) {
+                if (callback == record.mCallback) {
+                    recordToRemove = record;
+                    break;
+                }
+            }
+
+            if (recordToRemove != null) {
+                mCallbackRecords.remove(recordToRemove);
+            }
+        }
+    }
+
+    /**
+     * Release this session.
+     * Any operation on this session after calling this method will be ignored.
+     *
+     * @param stopMedia Should the device where the media is played
+     *                  be stopped after this session is released.
+     */
+    public void release(boolean stopMedia) {
+        mIsReleased = true;
+        mCallbackRecords.clear();
+        // TODO: Use stopMedia variable when the actual connection logic is implemented.
+    }
+
+    /**
+     * Callback class for getting updates on routes and session release.
+     */
+    public static class Callback {
+
+        /**
+         * Called when the session info has changed.
+         * TODO: When SessionInfo is introduced, uncomment below argument.
+         */
+        void onSessionInfoChanged(/* SessionInfo info */) {}
+
+        /**
+         * Called when the session is released. Session can be released by the controller using
+         * {@link #release(boolean)}, or by the {@link MediaRoute2ProviderService} itself.
+         * One can do clean-ups here.
+         *
+         * TODO: When SessionInfo is introduced, change the javadoc of releasing session on
+         * provider side.
+         */
+        void onReleased(int reason, boolean shouldStop) {}
+    }
+
+    private class CallbackRecord {
+        public final Callback mCallback;
+        public Executor mExecutor;
+
+        CallbackRecord(@NonNull Executor executor, @NonNull Callback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+    }
+}
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index a315c1e..6705b0c 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -33,14 +33,13 @@
 import android.graphics.ImageDecoder.ImageInfo;
 import android.graphics.ImageDecoder.Source;
 import android.graphics.Matrix;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Build;
 import android.os.CancellationSignal;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore.ThumbnailConstants;
+import android.provider.MediaStore;
 import android.util.Log;
 import android.util.Size;
 
@@ -77,15 +76,7 @@
     public static final int OPTIONS_RECYCLE_INPUT = 0x2;
 
     private static Size convertKind(int kind) {
-        if (kind == ThumbnailConstants.MICRO_KIND) {
-            return Point.convert(ThumbnailConstants.MICRO_SIZE);
-        } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
-            return Point.convert(ThumbnailConstants.FULL_SCREEN_SIZE);
-        } else if (kind == ThumbnailConstants.MINI_KIND) {
-            return Point.convert(ThumbnailConstants.MINI_SIZE);
-        } else {
-            throw new IllegalArgumentException("Unsupported kind: " + kind);
-        }
+        return MediaStore.Images.Thumbnails.getKindSize(kind);
     }
 
     private static class Resizer implements ImageDecoder.OnHeaderDecodedListener {
diff --git a/media/java/android/media/audio/common/AudioChannelMask.aidl b/media/java/android/media/audio/common/AudioChannelMask.aidl
new file mode 100644
index 0000000..b9b08e6
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioChannelMask.aidl
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * A channel mask per se only defines the presence or absence of a channel, not
+ * the order.
+ *
+ * The channel order convention is that channels are interleaved in order from
+ * least significant channel mask bit to most significant channel mask bit,
+ * with unused bits skipped. For example for stereo, LEFT would be first,
+ * followed by RIGHT.
+ * Any exceptions to this convention are noted at the appropriate API.
+ *
+ * AudioChannelMask is an opaque type and its internal layout should not be
+ * assumed as it may change in the future.  Instead, always use functions
+ * to examine it.
+ *
+ * These are the current representations:
+ *
+ *   REPRESENTATION_POSITION
+ *     is a channel mask representation for position assignment.  Each low-order
+ *     bit corresponds to the spatial position of a transducer (output), or
+ *     interpretation of channel (input).  The user of a channel mask needs to
+ *     know the context of whether it is for output or input.  The constants
+ *     OUT_* or IN_* apply to the bits portion.  It is not permitted for no bits
+ *     to be set.
+ *
+ *   REPRESENTATION_INDEX
+ *     is a channel mask representation for index assignment.  Each low-order
+ *     bit corresponds to a selected channel.  There is no platform
+ *     interpretation of the various bits.  There is no concept of output or
+ *     input.  It is not permitted for no bits to be set.
+ *
+ * All other representations are reserved for future use.
+ *
+ * Warning: current representation distinguishes between input and output, but
+ * this will not the be case in future revisions of the platform. Wherever there
+ * is an ambiguity between input and output that is currently resolved by
+ * checking the channel mask, the implementer should look for ways to fix it
+ * with additional information outside of the mask.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioChannelMask {
+    /**
+     * must be 0 for compatibility
+     */
+    REPRESENTATION_POSITION = 0,
+    /**
+     * 1 is reserved for future use
+     */
+    REPRESENTATION_INDEX = 2,
+    /**
+     * 3 is reserved for future use
+     *
+     *
+     * These can be a complete value of AudioChannelMask
+     */
+    NONE = 0x0,
+    INVALID = 0xC0000000,
+    /**
+     * These can be the bits portion of an AudioChannelMask
+     * with representation REPRESENTATION_POSITION.
+     *
+     *
+     * output channels
+     */
+    OUT_FRONT_LEFT = 0x1,
+    OUT_FRONT_RIGHT = 0x2,
+    OUT_FRONT_CENTER = 0x4,
+    OUT_LOW_FREQUENCY = 0x8,
+    OUT_BACK_LEFT = 0x10,
+    OUT_BACK_RIGHT = 0x20,
+    OUT_FRONT_LEFT_OF_CENTER = 0x40,
+    OUT_FRONT_RIGHT_OF_CENTER = 0x80,
+    OUT_BACK_CENTER = 0x100,
+    OUT_SIDE_LEFT = 0x200,
+    OUT_SIDE_RIGHT = 0x400,
+    OUT_TOP_CENTER = 0x800,
+    OUT_TOP_FRONT_LEFT = 0x1000,
+    OUT_TOP_FRONT_CENTER = 0x2000,
+    OUT_TOP_FRONT_RIGHT = 0x4000,
+    OUT_TOP_BACK_LEFT = 0x8000,
+    OUT_TOP_BACK_CENTER = 0x10000,
+    OUT_TOP_BACK_RIGHT = 0x20000,
+    OUT_TOP_SIDE_LEFT = 0x40000,
+    OUT_TOP_SIDE_RIGHT = 0x80000,
+    /**
+     * Haptic channel characteristics are specific to a device and
+     * only used to play device specific resources (eg: ringtones).
+     * The HAL can freely map A and B to haptic controllers, the
+     * framework shall not interpret those values and forward them
+     * from the device audio assets.
+     */
+    OUT_HAPTIC_A = 0x20000000,
+    OUT_HAPTIC_B = 0x10000000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+//    OUT_MONO = OUT_FRONT_LEFT,
+//    OUT_STEREO = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT),
+//    OUT_2POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_LOW_FREQUENCY),
+//    OUT_2POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+//    OUT_2POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+//    OUT_3POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+//    OUT_3POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+//    OUT_QUAD = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+//    OUT_QUAD_BACK = OUT_QUAD,
+//    /**
+//     * like OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_*
+//     */
+//    OUT_QUAD_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+//    OUT_SURROUND = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_BACK_CENTER),
+//    OUT_PENTA = (OUT_QUAD | OUT_FRONT_CENTER),
+//    OUT_5POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+//    OUT_5POINT1_BACK = OUT_5POINT1,
+//    /**
+//     * like OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_*
+//     */
+//    OUT_5POINT1_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+//    OUT_5POINT1POINT2 = (OUT_5POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+//    OUT_5POINT1POINT4 = (OUT_5POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+//    OUT_6POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_BACK_CENTER),
+//    /**
+//     * matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND
+//     */
+//    OUT_7POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+//    OUT_7POINT1POINT2 = (OUT_7POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+//    OUT_7POINT1POINT4 = (OUT_7POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+//    OUT_MONO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_HAPTIC_A),
+//    OUT_STEREO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A),
+//    OUT_HAPTIC_AB = (OUT_HAPTIC_A | OUT_HAPTIC_B),
+//    OUT_MONO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+//    OUT_STEREO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+    /**
+     * These are bits only, not complete values
+     *
+     *
+     * input channels
+     */
+    IN_LEFT = 0x4,
+    IN_RIGHT = 0x8,
+    IN_FRONT = 0x10,
+    IN_BACK = 0x20,
+    IN_LEFT_PROCESSED = 0x40,
+    IN_RIGHT_PROCESSED = 0x80,
+    IN_FRONT_PROCESSED = 0x100,
+    IN_BACK_PROCESSED = 0x200,
+    IN_PRESSURE = 0x400,
+    IN_X_AXIS = 0x800,
+    IN_Y_AXIS = 0x1000,
+    IN_Z_AXIS = 0x2000,
+    IN_BACK_LEFT = 0x10000,
+    IN_BACK_RIGHT = 0x20000,
+    IN_CENTER = 0x40000,
+    IN_LOW_FREQUENCY = 0x100000,
+    IN_TOP_LEFT = 0x200000,
+    IN_TOP_RIGHT = 0x400000,
+    IN_VOICE_UPLINK = 0x4000,
+    IN_VOICE_DNLINK = 0x8000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+//    IN_MONO = IN_FRONT,
+//    IN_STEREO = (IN_LEFT | IN_RIGHT),
+//    IN_FRONT_BACK = (IN_FRONT | IN_BACK),
+//    IN_6 = (IN_LEFT | IN_RIGHT | IN_FRONT | IN_BACK | IN_LEFT_PROCESSED | IN_RIGHT_PROCESSED),
+//    IN_2POINT0POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+//    IN_2POINT1POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+//    IN_3POINT0POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+//    IN_3POINT1POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+//    IN_5POINT1 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_BACK_LEFT | IN_BACK_RIGHT | IN_LOW_FREQUENCY),
+//    IN_VOICE_UPLINK_MONO = (IN_VOICE_UPLINK | IN_MONO),
+//    IN_VOICE_DNLINK_MONO = (IN_VOICE_DNLINK | IN_MONO),
+//    IN_VOICE_CALL_MONO = (IN_VOICE_UPLINK_MONO | IN_VOICE_DNLINK_MONO),
+//    COUNT_MAX = 30,
+//    INDEX_HDR = REPRESENTATION_INDEX << COUNT_MAX,
+//    INDEX_MASK_1 = INDEX_HDR | ((1 << 1) - 1),
+//    INDEX_MASK_2 = INDEX_HDR | ((1 << 2) - 1),
+//    INDEX_MASK_3 = INDEX_HDR | ((1 << 3) - 1),
+//    INDEX_MASK_4 = INDEX_HDR | ((1 << 4) - 1),
+//    INDEX_MASK_5 = INDEX_HDR | ((1 << 5) - 1),
+//    INDEX_MASK_6 = INDEX_HDR | ((1 << 6) - 1),
+//    INDEX_MASK_7 = INDEX_HDR | ((1 << 7) - 1),
+//    INDEX_MASK_8 = INDEX_HDR | ((1 << 8) - 1),
+//    INDEX_MASK_9 = INDEX_HDR | ((1 << 9) - 1),
+//    INDEX_MASK_10 = INDEX_HDR | ((1 << 10) - 1),
+//    INDEX_MASK_11 = INDEX_HDR | ((1 << 11) - 1),
+//    INDEX_MASK_12 = INDEX_HDR | ((1 << 12) - 1),
+//    INDEX_MASK_13 = INDEX_HDR | ((1 << 13) - 1),
+//    INDEX_MASK_14 = INDEX_HDR | ((1 << 14) - 1),
+//    INDEX_MASK_15 = INDEX_HDR | ((1 << 15) - 1),
+//    INDEX_MASK_16 = INDEX_HDR | ((1 << 16) - 1),
+//    INDEX_MASK_17 = INDEX_HDR | ((1 << 17) - 1),
+//    INDEX_MASK_18 = INDEX_HDR | ((1 << 18) - 1),
+//    INDEX_MASK_19 = INDEX_HDR | ((1 << 19) - 1),
+//    INDEX_MASK_20 = INDEX_HDR | ((1 << 20) - 1),
+//    INDEX_MASK_21 = INDEX_HDR | ((1 << 21) - 1),
+//    INDEX_MASK_22 = INDEX_HDR | ((1 << 22) - 1),
+//    INDEX_MASK_23 = INDEX_HDR | ((1 << 23) - 1),
+//    INDEX_MASK_24 = INDEX_HDR | ((1 << 24) - 1),
+}
diff --git a/media/java/android/media/audio/common/AudioConfig.aidl b/media/java/android/media/audio/common/AudioConfig.aidl
new file mode 100644
index 0000000..50dd796
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioConfig.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioOffloadInfo;
+
+/**
+ * Commonly used audio stream configuration parameters.
+ *
+ * {@hide}
+ */
+parcelable AudioConfig {
+    int sampleRateHz;
+    int channelMask;
+    AudioFormat format;
+    AudioOffloadInfo offloadInfo;
+    long frameCount;
+}
diff --git a/media/java/android/media/audio/common/AudioFormat.aidl b/media/java/android/media/audio/common/AudioFormat.aidl
new file mode 100644
index 0000000..aadc8e2
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioFormat.aidl
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * Audio format  is a 32-bit word that consists of:
+ *   main format field (upper 8 bits)
+ *   sub format field (lower 24 bits).
+ *
+ * The main format indicates the main codec type. The sub format field indicates
+ * options and parameters for each format. The sub format is mainly used for
+ * record to indicate for instance the requested bitrate or profile.  It can
+ * also be used for certain formats to give informations not present in the
+ * encoded audio stream (e.g. octet alignement for AMR).
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioFormat {
+   INVALID = 0xFFFFFFFF,
+   DEFAULT = 0,
+   PCM = 0x00000000,
+   MP3 = 0x01000000,
+   AMR_NB = 0x02000000,
+   AMR_WB = 0x03000000,
+   AAC = 0x04000000,
+   /**
+    * Deprecated, Use AAC_HE_V1
+    */
+   HE_AAC_V1 = 0x05000000,
+   /**
+    * Deprecated, Use AAC_HE_V2
+    */
+   HE_AAC_V2 = 0x06000000,
+   VORBIS = 0x07000000,
+   OPUS = 0x08000000,
+   AC3 = 0x09000000,
+   E_AC3 = 0x0A000000,
+   DTS = 0x0B000000,
+   DTS_HD = 0x0C000000,
+   /**
+    * IEC61937 is encoded audio wrapped in 16-bit PCM.
+    */
+   IEC61937 = 0x0D000000,
+   DOLBY_TRUEHD = 0x0E000000,
+   EVRC = 0x10000000,
+   EVRCB = 0x11000000,
+   EVRCWB = 0x12000000,
+   EVRCNW = 0x13000000,
+   AAC_ADIF = 0x14000000,
+   WMA = 0x15000000,
+   WMA_PRO = 0x16000000,
+   AMR_WB_PLUS = 0x17000000,
+   MP2 = 0x18000000,
+   QCELP = 0x19000000,
+   DSD = 0x1A000000,
+   FLAC = 0x1B000000,
+   ALAC = 0x1C000000,
+   APE = 0x1D000000,
+   AAC_ADTS = 0x1E000000,
+   SBC = 0x1F000000,
+   APTX = 0x20000000,
+   APTX_HD = 0x21000000,
+   AC4 = 0x22000000,
+   LDAC = 0x23000000,
+   /**
+    * Dolby Metadata-enhanced Audio Transmission
+    */
+   MAT = 0x24000000,
+   AAC_LATM = 0x25000000,
+   CELT = 0x26000000,
+   APTX_ADAPTIVE = 0x27000000,
+   LHDC = 0x28000000,
+   LHDC_LL = 0x29000000,
+   APTX_TWSP = 0x2A000000,
+   /**
+    * Deprecated
+    */
+   MAIN_MASK = 0xFF000000,
+   SUB_MASK = 0x00FFFFFF,
+   /**
+    * Subformats
+    */
+   PCM_SUB_16_BIT = 0x1,
+   PCM_SUB_8_BIT = 0x2,
+   PCM_SUB_32_BIT = 0x3,
+   PCM_SUB_8_24_BIT = 0x4,
+   PCM_SUB_FLOAT = 0x5,
+   PCM_SUB_24_BIT_PACKED = 0x6,
+   MP3_SUB_NONE = 0x0,
+   AMR_SUB_NONE = 0x0,
+   AAC_SUB_MAIN = 0x1,
+   AAC_SUB_LC = 0x2,
+   AAC_SUB_SSR = 0x4,
+   AAC_SUB_LTP = 0x8,
+   AAC_SUB_HE_V1 = 0x10,
+   AAC_SUB_SCALABLE = 0x20,
+   AAC_SUB_ERLC = 0x40,
+   AAC_SUB_LD = 0x80,
+   AAC_SUB_HE_V2 = 0x100,
+   AAC_SUB_ELD = 0x200,
+   AAC_SUB_XHE = 0x300,
+   VORBIS_SUB_NONE = 0x0,
+   E_AC3_SUB_JOC = 0x1,
+   MAT_SUB_1_0 = 0x1,
+   MAT_SUB_2_0 = 0x2,
+   MAT_SUB_2_1 = 0x3,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+//   /**
+//    * Aliases
+//    *
+//    *
+//    * note != AudioFormat.ENCODING_PCM_16BIT
+//    */
+//   PCM_16_BIT = (PCM | PCM_SUB_16_BIT),
+//   /**
+//    * note != AudioFormat.ENCODING_PCM_8BIT
+//    */
+//   PCM_8_BIT = (PCM | PCM_SUB_8_BIT),
+//   PCM_32_BIT = (PCM | PCM_SUB_32_BIT),
+//   PCM_8_24_BIT = (PCM | PCM_SUB_8_24_BIT),
+//   PCM_FLOAT = (PCM | PCM_SUB_FLOAT),
+//   PCM_24_BIT_PACKED = (PCM | PCM_SUB_24_BIT_PACKED),
+//   AAC_MAIN = (AAC | AAC_SUB_MAIN),
+//   AAC_LC = (AAC | AAC_SUB_LC),
+//   AAC_SSR = (AAC | AAC_SUB_SSR),
+//   AAC_LTP = (AAC | AAC_SUB_LTP),
+//   AAC_HE_V1 = (AAC | AAC_SUB_HE_V1),
+//   AAC_SCALABLE = (AAC | AAC_SUB_SCALABLE),
+//   AAC_ERLC = (AAC | AAC_SUB_ERLC),
+//   AAC_LD = (AAC | AAC_SUB_LD),
+//   AAC_HE_V2 = (AAC | AAC_SUB_HE_V2),
+//   AAC_ELD = (AAC | AAC_SUB_ELD),
+//   AAC_XHE = (AAC | AAC_SUB_XHE),
+//   AAC_ADTS_MAIN = (AAC_ADTS | AAC_SUB_MAIN),
+//   AAC_ADTS_LC = (AAC_ADTS | AAC_SUB_LC),
+//   AAC_ADTS_SSR = (AAC_ADTS | AAC_SUB_SSR),
+//   AAC_ADTS_LTP = (AAC_ADTS | AAC_SUB_LTP),
+//   AAC_ADTS_HE_V1 = (AAC_ADTS | AAC_SUB_HE_V1),
+//   AAC_ADTS_SCALABLE = (AAC_ADTS | AAC_SUB_SCALABLE),
+//   AAC_ADTS_ERLC = (AAC_ADTS | AAC_SUB_ERLC),
+//   AAC_ADTS_LD = (AAC_ADTS | AAC_SUB_LD),
+//   AAC_ADTS_HE_V2 = (AAC_ADTS | AAC_SUB_HE_V2),
+//   AAC_ADTS_ELD = (AAC_ADTS | AAC_SUB_ELD),
+//   AAC_ADTS_XHE = (AAC_ADTS | AAC_SUB_XHE),
+//   E_AC3_JOC = (E_AC3 | E_AC3_SUB_JOC),
+//   MAT_1_0 = (MAT | MAT_SUB_1_0),
+//   MAT_2_0 = (MAT | MAT_SUB_2_0),
+//   MAT_2_1 = (MAT | MAT_SUB_2_1),
+//   AAC_LATM_LC = (AAC_LATM | AAC_SUB_LC),
+//   AAC_LATM_HE_V1 = (AAC_LATM | AAC_SUB_HE_V1),
+//   AAC_LATM_HE_V2 = (AAC_LATM | AAC_SUB_HE_V2),
+}
diff --git a/media/java/android/media/audio/common/AudioOffloadInfo.aidl b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
new file mode 100644
index 0000000..ec10d71
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioStreamType;
+import android.media.audio.common.AudioUsage;
+
+/**
+ * Additional information about the stream passed to hardware decoders.
+ *
+ * {@hide}
+ */
+parcelable AudioOffloadInfo {
+    int sampleRateHz;
+    int channelMask;
+    AudioFormat format;
+    AudioStreamType streamType;
+    int bitRatePerSecond;
+    long durationMicroseconds;
+    boolean hasVideo;
+    boolean isStreaming;
+    int bitWidth;
+    int bufferSize;
+    AudioUsage usage;
+}
+
diff --git a/media/java/android/media/audio/common/AudioStreamType.aidl b/media/java/android/media/audio/common/AudioStreamType.aidl
new file mode 100644
index 0000000..c545667
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioStreamType.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ *  Audio streams
+ *
+ * Audio stream type describing the intended use case of a stream.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioStreamType {
+    DEFAULT = -1,
+    MIN = 0,
+    VOICE_CALL = 0,
+    SYSTEM = 1,
+    RING = 2,
+    MUSIC = 3,
+    ALARM = 4,
+    NOTIFICATION = 5,
+    BLUETOOTH_SCO = 6,
+    ENFORCED_AUDIBLE = 7,
+    DTMF = 8,
+    TTS = 9,
+    ACCESSIBILITY = 10,
+}
diff --git a/media/java/android/media/audio/common/AudioUsage.aidl b/media/java/android/media/audio/common/AudioUsage.aidl
new file mode 100644
index 0000000..ef34816
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioUsage.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioUsage {
+    UNKNOWN = 0,
+    MEDIA = 1,
+    VOICE_COMMUNICATION = 2,
+    VOICE_COMMUNICATION_SIGNALLING = 3,
+    ALARM = 4,
+    NOTIFICATION = 5,
+    NOTIFICATION_TELEPHONY_RINGTONE = 6,
+    ASSISTANCE_ACCESSIBILITY = 11,
+    ASSISTANCE_NAVIGATION_GUIDANCE = 12,
+    ASSISTANCE_SONIFICATION = 13,
+    GAME = 14,
+    VIRTUAL_SOURCE = 15,
+    ASSISTANT = 16,
+}
diff --git a/media/java/android/media/session/ICallback.aidl b/media/java/android/media/session/ICallback.aidl
deleted file mode 100644
index 322bffa..0000000
--- a/media/java/android/media/session/ICallback.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/* Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.session;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.media.session.MediaSession;
-import android.view.KeyEvent;
-
-/**
- * @hide
- */
-oneway interface ICallback {
-    void onMediaKeyEventDispatchedToMediaSession(in KeyEvent event,
-            in MediaSession.Token sessionToken);
-    void onMediaKeyEventDispatchedToMediaButtonReceiver(in KeyEvent event,
-            in ComponentName mediaButtonReceiver);
-
-    void onAddressedPlayerChangedToMediaSession(in MediaSession.Token sessionToken);
-    void onAddressedPlayerChangedToMediaButtonReceiver(in ComponentName mediaButtonReceiver);
-}
-
diff --git a/media/java/android/media/session/IOnMediaKeyEventDispatchedListener.aidl b/media/java/android/media/session/IOnMediaKeyEventDispatchedListener.aidl
new file mode 100644
index 0000000..90d9134
--- /dev/null
+++ b/media/java/android/media/session/IOnMediaKeyEventDispatchedListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.media.session.MediaSession;
+import android.view.KeyEvent;
+
+/**
+ * @hide
+ */
+oneway interface IOnMediaKeyEventDispatchedListener {
+    void onMediaKeyEventDispatched(in KeyEvent event, in String packageName,
+            in MediaSession.Token sessionToken);
+}
diff --git a/media/java/android/media/session/IOnMediaKeyEventSessionChangedListener.aidl b/media/java/android/media/session/IOnMediaKeyEventSessionChangedListener.aidl
new file mode 100644
index 0000000..9566e75
--- /dev/null
+++ b/media/java/android/media/session/IOnMediaKeyEventSessionChangedListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session;
+
+import android.media.session.MediaSession;
+
+/**
+ * @hide
+ */
+oneway interface IOnMediaKeyEventSessionChangedListener {
+    void onMediaKeyEventSessionChanged(in String packageName,
+            in MediaSession.Token mediaKeyEventSessionToken);
+}
+
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 01e6ed5..c8502a5 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -20,7 +20,8 @@
 import android.media.IRemoteVolumeController;
 import android.media.Session2Token;
 import android.media.session.IActiveSessionsListener;
-import android.media.session.ICallback;
+import android.media.session.IOnMediaKeyEventDispatchedListener;
+import android.media.session.IOnMediaKeyEventSessionChangedListener;
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession;
@@ -62,8 +63,12 @@
     // For PhoneWindowManager to precheck media keys
     boolean isGlobalPriorityActive();
 
-    void registerCallback(in ICallback callback);
-    void unregisterCallback(in ICallback callback);
+    void addOnMediaKeyEventDispatchedListener(in IOnMediaKeyEventDispatchedListener listener);
+    void removeOnMediaKeyEventDispatchedListener(in IOnMediaKeyEventDispatchedListener listener);
+    void addOnMediaKeyEventSessionChangedListener(
+            in IOnMediaKeyEventSessionChangedListener listener);
+    void removeOnMediaKeyEventSessionChangedListener(
+            in IOnMediaKeyEventSessionChangedListener listener);
     void setOnVolumeKeyLongPressListener(in IOnVolumeKeyLongPressListener listener);
     void setOnMediaKeyListener(in IOnMediaKeyListener listener);
 
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 92fb31b..a89dc5f 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -32,7 +32,6 @@
 import android.media.Session2Token;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -78,29 +77,33 @@
      */
     public static final int RESULT_MEDIA_KEY_HANDLED = 1;
     private final ISessionManager mService;
+    private final OnMediaKeyEventDispatchedListenerStub mOnMediaKeyEventDispatchedListenerStub =
+            new OnMediaKeyEventDispatchedListenerStub();
+    private final OnMediaKeyEventSessionChangedListenerStub
+            mOnMediaKeyEventSessionChangedListenerStub =
+            new OnMediaKeyEventSessionChangedListenerStub();
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
-    private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners
-            = new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
+    private final ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper> mListeners =
+            new ArrayMap<OnActiveSessionsChangedListener, SessionsChangedWrapper>();
     @GuardedBy("mLock")
     private final ArrayMap<OnSession2TokensChangedListener, Session2TokensChangedWrapper>
             mSession2TokensListeners = new ArrayMap<>();
     @GuardedBy("mLock")
-    private final CallbackStub mCbStub = new CallbackStub();
+    private final Map<OnMediaKeyEventDispatchedListener, Executor>
+            mOnMediaKeyEventDispatchedListeners = new HashMap<>();
     @GuardedBy("mLock")
-    private final Map<Callback, Executor> mCallbacks = new HashMap<>();
+    private final Map<OnMediaKeyEventSessionChangedListener, Executor>
+            mMediaKeyEventSessionChangedCallbacks = new HashMap<>();
     @GuardedBy("mLock")
-    private MediaSession.Token mCurMediaButtonSession;
+    private String mCurMediaKeyEventSessionPackage;
     @GuardedBy("mLock")
-    private ComponentName mCurMediaButtonReceiver;
+    private MediaSession.Token mCurMediaKeyEventSession;
 
     private Context mContext;
     private OnVolumeKeyLongPressListenerImpl mOnVolumeKeyLongPressListener;
     private OnMediaKeyListenerImpl mOnMediaKeyListener;
-    // TODO: Remove mLegacyCallback once Bluetooth app stop calling setCallback() method.
-    @GuardedBy("mLock")
-    private Callback mLegacyCallback;
 
     /**
      * @hide
@@ -756,89 +759,118 @@
     }
 
     /**
-     * Set a {@link Callback}.
-     *
-     * <p>System can only have a single callback, and the callback can only be set by
-     * Bluetooth service process.
-     *
-     * @param callback A {@link Callback}. {@code null} to reset.
-     * @param handler The handler on which the callback should be invoked, or {@code null}
-     *            if the callback should be invoked on the calling thread's looper.
-     * @hide
-     */
-    // TODO: Remove this method once Bluetooth app stop calling it.
-    public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
-        if (handler == null) {
-            handler = new Handler();
-        }
-        synchronized (mLock) {
-            if (mLegacyCallback != null) {
-                unregisterCallback(mLegacyCallback);
-            }
-            mLegacyCallback = callback;
-            if (callback != null) {
-                registerCallback(new HandlerExecutor(handler), callback);
-            }
-        }
-    }
-
-    /**
-     * Register a {@link Callback}.
+     * Add a {@link OnMediaKeyEventDispatchedListener}.
      *
      * @param executor The executor on which the callback should be invoked
-     * @param callback A {@link Callback}.
+     * @param listener A {@link OnMediaKeyEventDispatchedListener}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
-    public void registerCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull Callback callback) {
+    public void addOnMediaKeyEventDispatchedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnMediaKeyEventDispatchedListener listener) {
         if (executor == null) {
             throw new NullPointerException("executor shouldn't be null");
         }
-        if (callback == null) {
-            throw new NullPointerException("callback shouldn't be null");
+        if (listener == null) {
+            throw new NullPointerException("listener shouldn't be null");
         }
         synchronized (mLock) {
             try {
-                mCallbacks.put(callback, executor);
-                if (mCurMediaButtonSession != null) {
-                    executor.execute(
-                            () -> callback.onAddressedPlayerChanged(mCurMediaButtonSession));
-                } else if (mCurMediaButtonReceiver != null) {
-                    executor.execute(
-                            () -> callback.onAddressedPlayerChanged(mCurMediaButtonReceiver));
-                }
-
-                if (mCallbacks.size() == 1) {
-                    mService.registerCallback(mCbStub);
+                mOnMediaKeyEventDispatchedListeners.put(listener, executor);
+                if (mOnMediaKeyEventDispatchedListeners.size() == 1) {
+                    mService.addOnMediaKeyEventDispatchedListener(
+                            mOnMediaKeyEventDispatchedListenerStub);
                 }
             } catch (RemoteException e) {
-                Log.e(TAG, "Failed to set media key callback", e);
+                Log.e(TAG, "Failed to set media key listener", e);
             }
         }
     }
 
     /**
-     * Unregister a {@link Callback}.
+     * Remove a {@link OnMediaKeyEventDispatchedListener}.
      *
-     * @param callback A {@link Callback}.
+     * @param listener A {@link OnMediaKeyEventDispatchedListener}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
-    public void unregisterCallback(@NonNull Callback callback) {
-        if (callback == null) {
-            throw new NullPointerException("callback shouldn't be null");
+    public void removeOnMediaKeyEventDispatchedListener(
+            @NonNull OnMediaKeyEventDispatchedListener listener) {
+        if (listener == null) {
+            throw new NullPointerException("listener shouldn't be null");
         }
         synchronized (mLock) {
             try {
-                mCallbacks.remove(callback);
-                if (mCallbacks.size() == 0) {
-                    mService.unregisterCallback(mCbStub);
+                mOnMediaKeyEventDispatchedListeners.remove(listener);
+                if (mOnMediaKeyEventDispatchedListeners.size() == 0) {
+                    mService.removeOnMediaKeyEventDispatchedListener(
+                            mOnMediaKeyEventDispatchedListenerStub);
                 }
             } catch (RemoteException e) {
-                Log.e(TAG, "Failed to set media key callback", e);
+                Log.e(TAG, "Failed to set media key event dispatched listener", e);
+            }
+        }
+    }
+
+    /**
+     * Add a {@link OnMediaKeyEventDispatchedListener}.
+     *
+     * @param executor The executor on which the callback should be invoked
+     * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void addOnMediaKeyEventSessionChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnMediaKeyEventSessionChangedListener listener) {
+        if (executor == null) {
+            throw new NullPointerException("executor shouldn't be null");
+        }
+        if (listener == null) {
+            throw new NullPointerException("listener shouldn't be null");
+        }
+        synchronized (mLock) {
+            try {
+                mMediaKeyEventSessionChangedCallbacks.put(listener, executor);
+                executor.execute(
+                        () -> listener.onMediaKeyEventSessionChanged(
+                                mCurMediaKeyEventSessionPackage, mCurMediaKeyEventSession));
+                if (mMediaKeyEventSessionChangedCallbacks.size() == 1) {
+                    mService.addOnMediaKeyEventSessionChangedListener(
+                            mOnMediaKeyEventSessionChangedListenerStub);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to set media key listener", e);
+            }
+        }
+    }
+
+    /**
+     * Remove a {@link OnMediaKeyEventSessionChangedListener}.
+     *
+     * @param listener A {@link OnMediaKeyEventSessionChangedListener}.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void removeOnMediaKeyEventSessionChangedListener(
+            @NonNull OnMediaKeyEventSessionChangedListener listener) {
+        if (listener == null) {
+            throw new NullPointerException("listener shouldn't be null");
+        }
+        synchronized (mLock) {
+            try {
+                mMediaKeyEventSessionChangedCallbacks.remove(listener);
+                if (mMediaKeyEventSessionChangedCallbacks.size() == 0) {
+                    mService.removeOnMediaKeyEventSessionChangedListener(
+                            mOnMediaKeyEventSessionChangedListenerStub);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to set media key listener", e);
             }
         }
     }
@@ -900,54 +932,46 @@
     }
 
     /**
-     * Callbacks for the media session service.
-     *
-     * <p>Called when a media key event is dispatched or the addressed player is changed.
-     * The addressed player is either the media session or the media button receiver that will
-     * receive media key events.
+     * Listener to receive when the media session service
      * @hide
      */
     @SystemApi
-    public static abstract class Callback {
+    public interface OnMediaKeyEventDispatchedListener {
         /**
-         * Called when a media key event is dispatched to the media session
-         * through the media session service.
+         * 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
          *
          * @param event Dispatched media key event.
-         * @param sessionToken The media session's token.
+         * @param packageName Package
+         * @param sessionToken The media session's token. Can be {@code null}.
          */
-        public abstract void onMediaKeyEventDispatched(KeyEvent event,
-                MediaSession.Token sessionToken);
+        default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName,
+                @NonNull MediaSession.Token sessionToken) { }
+    }
 
+    /**
+     * Listener to receive changes in the media key event session, which would receive the media key
+     * event unless specified.
+     * @hide
+     */
+    @SystemApi
+    public interface OnMediaKeyEventSessionChangedListener {
         /**
-         * Called when a media key event is dispatched to the media button receiver
-         * through the media session service.
-         * <p>MediaSessionService may broadcast key events to the media button receiver
-         * when reviving playback after the media session is released.
+         * Called when the media key session is changed to the given media session. The key event
+         * session is the media session which would receive key event by default, unless the caller
+         * has specified the target.
+         * <p>
+         * The session token can be {@link null} if the media button session is unset. In that case,
+         * framework would dispatch to the last sessions's media button receiver.
          *
-         * @param event Dispatched media key event.
-         * @param mediaButtonReceiver The media button receiver.
+         * @param packageName The package name who would receive the media key event. Can be empty.
+         * @param sessionToken The media session's token. Can be {@code null.}
          */
-        public abstract void onMediaKeyEventDispatched(KeyEvent event,
-                ComponentName mediaButtonReceiver);
-
-        /**
-         * Called when the addressed player is changed to a media session.
-         * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
-         * {@link #registerCallback} if the addressed player exists.
-         *
-         * @param sessionToken The media session's token.
-         */
-        public abstract void onAddressedPlayerChanged(MediaSession.Token sessionToken);
-
-        /**
-         * Called when the addressed player is changed to the media button receiver.
-         * <p>One of the {@ #onAddressedPlayerChanged} will be also called immediately after
-         * {@link #registerCallback} if the addressed player exists.
-         *
-         * @param mediaButtonReceiver The media button receiver.
-         */
-        public abstract void onAddressedPlayerChanged(ComponentName mediaButtonReceiver);
+        default void onMediaKeyEventSessionChanged(@NonNull String packageName,
+                @Nullable MediaSession.Token sessionToken) { }
     }
 
     /**
@@ -1149,50 +1173,35 @@
         }
     }
 
-    private final class CallbackStub extends ICallback.Stub {
+    private final class OnMediaKeyEventDispatchedListenerStub
+            extends IOnMediaKeyEventDispatchedListener.Stub {
 
         @Override
-        public void onMediaKeyEventDispatchedToMediaSession(KeyEvent event,
+        public void onMediaKeyEventDispatched(KeyEvent event, String packageName,
                 MediaSession.Token sessionToken) {
             synchronized (mLock) {
-                for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
+                for (Map.Entry<OnMediaKeyEventDispatchedListener, Executor> e
+                        : mOnMediaKeyEventDispatchedListeners.entrySet()) {
                     e.getValue().execute(
-                            () -> e.getKey().onMediaKeyEventDispatched(event, sessionToken));
+                            () -> e.getKey().onMediaKeyEventDispatched(event, packageName,
+                                    sessionToken));
                 }
             }
         }
+    }
 
+    private final class OnMediaKeyEventSessionChangedListenerStub
+            extends IOnMediaKeyEventSessionChangedListener.Stub {
         @Override
-        public void onMediaKeyEventDispatchedToMediaButtonReceiver(KeyEvent event,
-                ComponentName mediaButtonReceiver) {
+        public void onMediaKeyEventSessionChanged(String packageName,
+                MediaSession.Token sessionToken) {
             synchronized (mLock) {
-                for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
-                    e.getValue().execute(
-                            () -> e.getKey().onMediaKeyEventDispatched(event, mediaButtonReceiver));
-                }
-            }
-        }
-
-        @Override
-        public void onAddressedPlayerChangedToMediaSession(MediaSession.Token sessionToken) {
-            synchronized (mLock) {
-                mCurMediaButtonSession = sessionToken;
-                mCurMediaButtonReceiver = null;
-                for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
-                    e.getValue().execute(() -> e.getKey().onAddressedPlayerChanged(sessionToken));
-                }
-            }
-        }
-
-        @Override
-        public void onAddressedPlayerChangedToMediaButtonReceiver(
-                ComponentName mediaButtonReceiver) {
-            synchronized (mLock) {
-                mCurMediaButtonSession = null;
-                mCurMediaButtonReceiver = mediaButtonReceiver;
-                for (Map.Entry<Callback, Executor> e : mCallbacks.entrySet()) {
-                    e.getValue().execute(() -> e.getKey().onAddressedPlayerChanged(
-                            mediaButtonReceiver));
+                mCurMediaKeyEventSessionPackage = packageName;
+                mCurMediaKeyEventSession = sessionToken;
+                for (Map.Entry<OnMediaKeyEventSessionChangedListener, Executor> e
+                        : mMediaKeyEventSessionChangedCallbacks.entrySet()) {
+                    e.getValue().execute(() -> e.getKey().onMediaKeyEventSessionChanged(packageName,
+                            sessionToken));
                 }
             }
         }
diff --git a/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
new file mode 100644
index 0000000..3dbc705
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * A recognition confidence level.
+ * This type is used to represent either a threshold or an actual detection confidence level.
+ *
+ * {@hide}
+ */
+parcelable ConfidenceLevel {
+    /** user ID. */
+    int userId;
+    /**
+     * Confidence level in percent (0 - 100).
+     * <ul>
+     * <li>Min level for recognition configuration
+     * <li>Detected level for recognition event.
+     * </ul>
+     */
+    int levelPercent;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
new file mode 100644
index 0000000..149c1cd
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+
+/**
+ * Main interface for a client to get notifications of events coming from this module.
+ *
+ * {@hide}
+ */
+oneway interface ISoundTriggerCallback {
+    /**
+     * Invoked whenever a recognition event is triggered (typically, on recognition, but also in
+     * case of external aborting of a recognition or a forced recognition event - see the status
+     * code in the event for determining).
+     */
+    void onRecognition(int modelHandle, in RecognitionEvent event);
+     /**
+      * Invoked whenever a phrase recognition event is triggered (typically, on recognition, but
+      * also in case of external aborting of a recognition or a forced recognition event - see the
+      * status code in the event for determining).
+      */
+    void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event);
+    /**
+     * Notifies the client the recognition has become available after previously having been
+     * unavailable, or vice versa. This method will always be invoked once immediately after
+     * attachment, and then every time there is a change in availability.
+     * When availability changes from available to unavailable, all active recognitions are aborted,
+     * and this event will be sent in addition to the abort event.
+     */
+    void onRecognitionAvailabilityChange(boolean available);
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
new file mode 100644
index 0000000..8033307
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+
+/**
+ * Main entry point into this module.
+ *
+ * Allows the client to enumerate the available soundtrigger devices and their capabilities, then
+ * attach to either one of them in order to use it.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerMiddlewareService {
+    /**
+     * Query the available modules and their capabilities.
+     */
+    SoundTriggerModuleDescriptor[] listModules();
+
+    /**
+     * Attach to one of the available modules.
+     * listModules() must be called prior to calling this method and the provided handle must be
+     * one of the handles from the returned list.
+     */
+    ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback);
+
+    /**
+     * Notify the service that external input capture is taking place. This may cause some of the
+     * active recognitions to be aborted.
+     */
+    void setExternalCaptureState(boolean active);
+}
\ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
new file mode 100644
index 0000000..c4a5785
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+
+/**
+ * A sound-trigger module.
+ *
+ * This interface allows a client to operate a sound-trigger device, intended for low-power
+ * detection of various sound patterns, represented by a "sound model".
+ *
+ * Basic operation is to load a sound model (either a generic one or a "phrase" model), then
+ * initiate recognition on this model. A trigger will be delivered asynchronously via a callback
+ * provided by the caller earlier, when attaching to this interface.
+ *
+ * In additon to recognition events, this module will also produce abort events in cases where
+ * recognition has been externally preempted.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerModule {
+    /**
+     * Load a sound model. Will return a handle to the model on success or will throw a
+     * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+     * (for example, lack of resources of loading a model at the time of call.
+     * Model must eventually be unloaded using {@link #unloadModel(int)} prior to detaching.
+     *
+     * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+     * resources required for loading the model are currently consumed by other clients.
+     */
+    int loadModel(in SoundModel model);
+
+    /**
+     * Load a phrase sound model. Will return a handle to the model on success or will throw a
+     * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+     * (for example, lack of resources of loading a model at the time of call.
+     * Model must eventually be unloaded using unloadModel prior to detaching.
+     *
+     * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+     * resources required for loading the model are currently consumed by other clients.
+     */
+    int loadPhraseModel(in PhraseSoundModel model);
+
+    /**
+     * Unload a model, previously loaded with loadModel or loadPhraseModel. After unloading, model
+     * can no longer be used for recognition and the resources occupied by it are released.
+     * Model must not be active at the time of unloading. Cient may call stopRecognition to ensure
+     * that.
+     */
+    void unloadModel(int modelHandle);
+
+    /**
+     * Initiate recognition on a previously loaded model.
+     * Recognition event would eventually be delivered via the client-provided callback, typically
+     * supplied during attachment to this interface.
+     *
+     * Once a recognition event is passed to the client, the recognition automatically become
+     * inactive, unless the event is of the RecognitionStatus.FORCED kind. Client can also shut down
+     * the recognition explicitly, via stopRecognition.
+     */
+    void startRecognition(int modelHandle, in RecognitionConfig config);
+
+    /**
+     * Stop a recognition of a previously active recognition. Will NOT generate a recognition event.
+     * This call is idempotent - calling it on an inactive model has no effect. However, it must
+     * only be used with a loaded model handle.
+     */
+    void stopRecognition(int modelHandle);
+
+    /**
+     * Force generation of a recognition event. Handle must be that of a loaded model. If
+     * recognition is inactive, will do nothing. If recognition is active, will asynchronously
+     * deliever an event with RecognitionStatus.FORCED status and leave recognition in active state.
+     * To avoid any race conditions, once an event signalling the automatic stopping of recognition
+     * is sent, no more forced events will get sent (even if previously requested) until recognition
+     * is explicitly started again.
+     *
+     * Since not all module implementations support this feature, may throw a
+     * ServiceSpecificException with an OPERATION_NOT_SUPPORTED status.
+     */
+    void forceRecognitionEvent(int modelHandle);
+
+    /**
+     * Set a model specific parameter 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.
+     * It is expected to check if the handle supports the parameter via the
+     * queryModelParameterSupport API prior to calling this method.
+     *
+     * @param modelHandle The sound model handle indicating which model to modify parameters
+     * @param modelParam Parameter to set which will be validated against the
+     *                   ModelParameter type.
+     * @param value The value to set for the given model parameter
+     */
+    void setModelParameter(int modelHandle, ModelParameter modelParam, int value);
+
+    /**
+     * Get a model specific parameter. 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 ModelParameter for parameter default values.
+     * It is expected to check if the handle supports the parameter via the
+     * queryModelParameterSupport API prior to calling this method.
+     *
+     * @param modelHandle The sound model associated with given modelParam
+     * @param modelParam Parameter to set which will be validated against the
+     *                   ModelParameter type.
+     * @return Value set to the requested parameter.
+     */
+    int getModelParameter(int modelHandle, ModelParameter modelParam);
+
+    /**
+     * Determine if parameter control is supported for the given model handle, and its valid value
+     * range if it is.
+     *
+     * @param modelHandle The sound model handle indicating which model to query
+     * @param modelParam Parameter to set which will be validated against the
+     *                   ModelParameter type.
+     * @return If parameter is supported, the return value is its valid range, otherwise null.
+     */
+    @nullable ModelParameterRange queryModelParameterSupport(int modelHandle,
+                                                             ModelParameter modelParam);
+
+    /**
+     * Detach from the module, releasing any active resources.
+     * This will ensure the client callback is no longer called after this call returns.
+     * All models must have been unloaded prior to calling this method.
+     */
+    void detach();
+}
\ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl b/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
new file mode 100644
index 0000000..0993627
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Model specific parameters to be used with parameter set and get APIs.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum ModelParameter {
+    /**
+     * Placeholder for invalid model parameter used for returning error or
+     * passing an invalid value.
+     */
+    INVALID = -1,
+
+    /**
+     * 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.
+     */
+    THRESHOLD_FACTOR = 0,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
new file mode 100644
index 0000000..d6948a8
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Value range for a model parameter.
+ *
+ * {@hide}
+ */
+parcelable ModelParameterRange {
+    /** Minimum (inclusive) */
+    int minInclusive;
+    /** Maximum (inclusive) */
+    int maxInclusive;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/Phrase.aidl b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
new file mode 100644
index 0000000..98a489f8
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Key phrase descriptor.
+ *
+ * {@hide}
+ */
+parcelable Phrase {
+    /** Unique keyphrase ID assigned at enrollment time. */
+    int id;
+    /** Recognition modes supported by this key phrase (bitfield of RecognitionMode enum). */
+    int recognitionModes;
+    /** List of users IDs associated with this key phrase. */
+    int[] users;
+    /** Locale - Java Locale style (e.g. en_US). */
+    String locale;
+    /** Phrase text. */
+    String text;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
new file mode 100644
index 0000000..6a3ec61
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+
+/**
+ * An event that gets sent to indicate a phrase recognition (or aborting of the recognition
+   process).
+ * {@hide}
+ */
+parcelable PhraseRecognitionEvent {
+    /** Common recognition event. */
+    RecognitionEvent common;
+    /** List of descriptors for each recognized key phrase */
+    PhraseRecognitionExtra[] phraseExtras;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
new file mode 100644
index 0000000..cb96bf3
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+
+/**
+ * Specialized recognition event for key phrase detection.
+ * {@hide}
+ */
+parcelable PhraseRecognitionExtra {
+    // TODO(ytai): Constants / enums.
+
+    /** keyphrase ID */
+    int id;
+    /** recognition modes used for this keyphrase */
+    int recognitionModes;
+    /** confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER */
+    int confidenceLevel;
+    /** number of user confidence levels */
+    ConfidenceLevel[] levels;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
new file mode 100644
index 0000000..81028c1
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.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;
+
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.Phrase;
+
+/**
+ * Specialized sound model for key phrase detection.
+ * Proprietary representation of key phrases in binary data must match
+ * information indicated by phrases field.
+ * {@hide}
+ */
+parcelable PhraseSoundModel {
+    /** Common part of sound model descriptor */
+    SoundModel common;
+    /** List of descriptors for key phrases supported by this sound model */
+    Phrase[] phrases;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
new file mode 100644
index 0000000..c7642e8
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+
+/**
+ * Configuration for tuning behavior of an active recognition process.
+ * {@hide}
+ */
+parcelable RecognitionConfig {
+    /* Capture and buffer audio for this recognition instance. */
+    boolean captureRequested;
+
+    /* Configuration for each key phrase. */
+    PhraseRecognitionExtra[] phraseRecognitionExtras;
+
+    /** Opaque capture configuration data. */
+    byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
new file mode 100644
index 0000000..de4d060
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * An event that gets sent to indicate a recognition (or aborting of the recognition process).
+ * {@hide}
+ */
+parcelable RecognitionEvent {
+    /** Recognition status. */
+    RecognitionStatus status;
+    /** Event type, same as sound model type. */
+    SoundModelType type;
+    /** Is it possible to capture audio from this utterance buffered by the implementation. */
+    boolean captureAvailable;
+    /* Audio session ID. framework use. */
+    int captureSession;
+    /**
+     * Delay in ms between end of model detection and start of audio available for capture.
+     * A negative value is possible (e.g. if key phrase is also available for Capture.
+     */
+    int captureDelayMs;
+    /** Duration in ms of audio captured before the start of the trigger. 0 if none. */
+    int capturePreambleMs;
+    /** If true, the 'data' field below contains the capture of the trigger sound. */
+    boolean triggerInData;
+    /**
+     * Audio format of either the trigger in event data or to use for capture of the rest of the
+     * utterance.
+     */
+    AudioConfig audioConfig;
+    /** Additional data. */
+    byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl
new file mode 100644
index 0000000..d8bfff4
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionMode.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;
+
+/**
+ * Recognition mode.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionMode {
+    /** Simple voice trigger. */
+    VOICE_TRIGGER       = 0x1,
+    /** Trigger only if one user in model identified. */
+    USER_IDENTIFICATION = 0x2,
+    /** Trigger only if one user in model authenticated. */
+    USER_AUTHENTICATION = 0x4,
+    /** Generic sound trigger. */
+    GENERIC_TRIGGER     = 0x8,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
new file mode 100644
index 0000000..d563edc
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * A status for indicating the type of a recognition event.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionStatus {
+    /** Recognition success. */
+    SUCCESS = 0,
+    /** Recognition aborted (e.g. capture preempted by another use-case. */
+    ABORTED = 1,
+    /** Recognition failure. */
+    FAILURE = 2,
+    /**
+    * Recognition event was triggered by a forceRecognitionEvent request, not by the DSP.
+    * Note that forced detections *do not* stop the active recognition, unlike the other types.
+    */
+    FORCED = 3
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
new file mode 100644
index 0000000..fba1ee50
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * Base sound model descriptor. This struct can be extended for various specific types by way of
+ * aggregation.
+ * {@hide}
+ */
+parcelable SoundModel {
+    /** Model type. */
+    SoundModelType type;
+    /** Unique sound model ID. */
+    String uuid;
+    /**
+     * Unique vendor ID. Identifies the engine the sound model
+     * was build for */
+    String vendorUuid;
+    /** Opaque data transparent to Android framework */
+    byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
new file mode 100644
index 0000000..f2abc9a
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * Sound model type.
+ * {@hide}
+ */
+@Backing(type="int")
+enum SoundModelType {
+    /** Unspecified sound model type */
+    UNKNOWN = -1,
+    /** Key phrase sound models */
+    KEYPHRASE = 0,
+    /** All models other than keyphrase */
+    GENERIC = 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
new file mode 100644
index 0000000..667135f
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+/**
+ * A descriptor of an available sound trigger module, containing the handle used to reference the
+ * module, as well its capabilities.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleDescriptor {
+    /** Module handle to be used for attaching to it. */
+    int handle;
+    /** Module capabilities. */
+    SoundTriggerModuleProperties properties;
+}
+
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
new file mode 100644
index 0000000..1a3b402
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+/**
+ * Capabilities of a sound trigger module.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleProperties {
+    /** Implementor name */
+    String   implementor;
+    /** Implementation description */
+    String   description;
+    /** Implementation version */
+    int version;
+    /**
+     * Unique implementation ID. The UUID must change with each version of
+       the engine implementation */
+    String     uuid;
+    /** Maximum number of concurrent sound models loaded */
+    int maxSoundModels;
+    /** Maximum number of key phrases */
+    int maxKeyPhrases;
+    /** Maximum number of concurrent users detected */
+    int maxUsers;
+    /** All supported modes. e.g RecognitionMode.VOICE_TRIGGER */
+    int recognitionModes;
+    /** Supports seamless transition from detection to capture */
+    boolean     captureTransition;
+    /** Maximum buffering capacity in ms if captureTransition is true */
+    int maxBufferMs;
+    /** Supports capture by other use cases while detection is active */
+    boolean     concurrentCapture;
+    /** Returns the trigger capture in event */
+    boolean     triggerInEvent;
+    /**
+     * Rated power consumption when detection is active with TDB
+     * silence/sound/speech ratio */
+    int powerConsumptionMw;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
new file mode 100644
index 0000000..d8f9d8f
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum Status {
+    /** Success. */
+    SUCCESS = 0,
+    /** Failure due to resource contention. This is typically a temporary condition. */
+    RESOURCE_CONTENTION = 1,
+    /** Operation is not supported in this implementation. This is a permanent condition. */
+    OPERATION_NOT_SUPPORTED = 2,
+}
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 16ba63b..f3c071a 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -16,8 +16,10 @@
 
 package android.mtp;
 
+import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.ContentProviderClient;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -422,13 +424,13 @@
         }
         // Add the new file to MediaProvider
         if (succeeded) {
-            MediaStore.scanFile(mContext, obj.getPath().toFile());
+            MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
         }
     }
 
     @VisibleForNative
     private void rescanFile(String path, int handle, int format) {
-        MediaStore.scanFile(mContext, new File(path));
+        MediaStore.scanFile(mContext.getContentResolver(), new File(path));
     }
 
     @VisibleForNative
@@ -577,7 +579,7 @@
         try {
             // note - we are relying on a special case in MediaProvider.update() to update
             // the paths for all children in the case where this is a directory.
-            final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+            final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
             mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in mMediaProvider.update", e);
@@ -587,13 +589,13 @@
         if (obj.isDir()) {
             // for directories, check if renamed from something hidden to something non-hidden
             if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
-                MediaStore.scanFile(mContext, newPath.toFile());
+                MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
             }
         } else {
             // for files, check if renamed from .nomedia to something else
             if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
                     && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
-                MediaStore.scanFile(mContext, newPath.getParent().toFile());
+                MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
             }
         }
         return MtpConstants.RESPONSE_OK;
@@ -658,11 +660,11 @@
                 // Old parent exists in MediaProvider - perform a move
                 // note - we are relying on a special case in MediaProvider.update() to update
                 // the paths for all children in the case where this is a directory.
-                final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+                final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
                 mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
             } else {
                 // Old parent doesn't exist - add the object
-                MediaStore.scanFile(mContext, path.toFile());
+                MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in mMediaProvider.update", e);
@@ -689,7 +691,7 @@
         if (!success) {
             return;
         }
-        MediaStore.scanFile(mContext, obj.getPath().toFile());
+        MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
     }
 
     @VisibleForNative
@@ -873,7 +875,7 @@
     }
 
     private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
-        final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+        final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
 
         int ret = -1;
         Cursor c = null;
@@ -893,7 +895,7 @@
     }
 
     private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
-        final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+        final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
         try {
             // Delete the object(s) from MediaProvider, but ignore errors.
             if (isDir) {
@@ -909,7 +911,7 @@
             String[] whereArgs = new String[]{path.toString()};
             if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
                 if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
-                    MediaStore.scanFile(mContext, path.getParent().toFile());
+                    MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
                 }
             } else {
                 Log.i(TAG, "Mediaprovider didn't delete " + path);
@@ -921,71 +923,12 @@
 
     @VisibleForNative
     private int[] getObjectReferences(int handle) {
-        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
-        if (obj == null)
-            return null;
-        // Translate this handle to the MediaProvider Handle
-        handle = findInMedia(obj, obj.getPath());
-        if (handle == -1)
-            return null;
-        Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
-        Cursor c = null;
-        try {
-            c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
-            if (c == null) {
-                return null;
-            }
-                ArrayList<Integer> result = new ArrayList<>();
-                while (c.moveToNext()) {
-                    // Translate result handles back into handles for this session.
-                    String refPath = c.getString(0);
-                    MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
-                    if (refObj != null) {
-                        result.add(refObj.getId());
-                    }
-                }
-                return result.stream().mapToInt(Integer::intValue).toArray();
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in getObjectList", e);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
         return null;
     }
 
     @VisibleForNative
     private int setObjectReferences(int handle, int[] references) {
-        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
-        if (obj == null)
-            return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
-        // Translate this handle to the MediaProvider Handle
-        handle = findInMedia(obj, obj.getPath());
-        if (handle == -1)
-            return MtpConstants.RESPONSE_GENERAL_ERROR;
-        Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
-        ArrayList<ContentValues> valuesList = new ArrayList<>();
-        for (int id : references) {
-            // Translate each reference id to the MediaProvider Id
-            MtpStorageManager.MtpObject refObj = mManager.getObject(id);
-            if (refObj == null)
-                continue;
-            int refHandle = findInMedia(refObj, refObj.getPath());
-            if (refHandle == -1)
-                continue;
-            ContentValues values = new ContentValues();
-            values.put(Files.FileColumns._ID, refHandle);
-            valuesList.add(values);
-        }
-        try {
-            if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
-                return MtpConstants.RESPONSE_OK;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "RemoteException in setObjectReferences", e);
-        }
-        return MtpConstants.RESPONSE_GENERAL_ERROR;
+        return MtpConstants.RESPONSE_OPERATION_NOT_SUPPORTED;
     }
 
     @VisibleForNative
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index 65d0fef..c7dbca6 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -41,11 +41,7 @@
         mDescription = volume.getDescription(null);
         mRemovable = volume.isRemovable();
         mMaxFileSize = volume.getMaxFileSize();
-        if (volume.isPrimary()) {
-            mVolumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
-        } else {
-            mVolumeName = volume.getNormalizedUuid();
-        }
+        mVolumeName = volume.getMediaStoreVolumeName();
     }
 
     /**
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 05aaa82..20980a9 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -742,11 +742,11 @@
     return OK;
 }
 
-status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const {
-    mediametrics_handle_t reply2 = MediaAnalyticsItem::convert(reply);
+status_t JMediaCodec::getMetrics(JNIEnv *, mediametrics::Item * &reply) const {
+    mediametrics_handle_t reply2 = mediametrics::Item::convert(reply);
     status_t status = mCodec->getMetrics(reply2);
     // getMetrics() updates reply2, pass the converted update along to our caller.
-    reply = MediaAnalyticsItem::convert(reply2);
+    reply = mediametrics::Item::convert(reply2);
     return status;
 }
 
@@ -1850,7 +1850,7 @@
     }
 
     // get what we have for the metrics from the codec
-    MediaAnalyticsItem *item = 0;
+    mediametrics::Item *item = 0;
 
     status_t err = codec->getMetrics(env, item);
     if (err != OK) {
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index dfe30a3..ce1c805 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -21,7 +21,7 @@
 
 #include "jni.h"
 
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
 #include <media/hardware/CryptoAPI.h>
 #include <media/stagefright/foundation/ABase.h>
 #include <media/stagefright/foundation/AHandler.h>
@@ -124,7 +124,7 @@
 
     status_t getCodecInfo(JNIEnv *env, jobject *codecInfo) const;
 
-    status_t getMetrics(JNIEnv *env, MediaAnalyticsItem * &reply) const;
+    status_t getMetrics(JNIEnv *env, mediametrics::Item * &reply) const;
 
     status_t setParameters(const sp<AMessage> &params);
 
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index f5ae9d0..528dc62 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -913,7 +913,7 @@
     }
 
     // build and return the Bundle
-    std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+    std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
     item->readFromParcel(reply);
     jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
 
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index d315154..e17a617 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,11 +18,12 @@
 
 #include <binder/Parcel.h>
 #include <jni.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
 #include <nativehelper/JNIHelp.h>
 
 #include "android_media_MediaMetricsJNI.h"
 #include "android_os_Parcel.h"
+#include "android_runtime/AndroidRuntime.h"
 
 // This source file is compiled and linked into:
 // core/jni/ (libandroid_runtime.so)
@@ -52,7 +53,7 @@
     const jmethodID constructID;
     jobject const bundle;
 
-    // We use templated put to access MediaAnalyticsItem based on data type not type enum.
+    // We use templated put to access mediametrics::Item based on data type not type enum.
     // See std::variant and std::visit.
     template<typename T>
     void put(jstring keyName, const T& value) = delete;
@@ -97,7 +98,7 @@
 
 // place the attributes into a java PersistableBundle object
 jobject MediaMetricsJNI::writeMetricsToBundle(
-        JNIEnv* env, MediaAnalyticsItem *item, jobject bundle)
+        JNIEnv* env, mediametrics::Item *item, jobject bundle)
 {
     BundleHelper bh(env, bundle);
 
@@ -124,6 +125,28 @@
     return bh.bundle;
 }
 
+// Implementation of MediaMetrics.native_submit_bytebuffer(),
+// Delivers the byte buffer to the mediametrics service.
+static jint android_media_MediaMetrics_submit_bytebuffer(
+        JNIEnv* env, jobject thiz, jobject byteBuffer, jint length)
+{
+    const jbyte* buffer =
+            reinterpret_cast<const jbyte*>(env->GetDirectBufferAddress(byteBuffer));
+    if (buffer == nullptr) {
+        ALOGE("Error retrieving source of audio data to play, can't play");
+        return (jint)BAD_VALUE;
+    }
+
+    // TODO: directly record item to MediaMetrics service.
+    mediametrics::Item item;
+    if (item.readFromByteString((char *)buffer, length) != NO_ERROR) {
+        ALOGW("%s: cannot read from byte string", __func__);
+        return (jint)BAD_VALUE;
+    }
+    item.selfrecord();
+    return (jint)NO_ERROR;
+}
+
 // Helper function to convert a native PersistableBundle to a Java
 // PersistableBundle.
 jobject MediaMetricsJNI::nativeToJavaPersistableBundle(JNIEnv *env,
@@ -191,5 +214,18 @@
     return newBundle;
 }
 
-};  // namespace android
+// ----------------------------------------------------------------------------
 
+static constexpr JNINativeMethod gMethods[] = {
+    {"native_submit_bytebuffer", "(Ljava/nio/ByteBuffer;I)I",
+            (void *)android_media_MediaMetrics_submit_bytebuffer},
+};
+
+// Registers the native methods, called from core/jni/AndroidRuntime.cpp
+int register_android_media_MediaMetrics(JNIEnv *env)
+{
+    return AndroidRuntime::registerNativeMethods(
+            env, "android/media/MediaMetrics", gMethods, std::size(gMethods));
+}
+
+};  // namespace android
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h
index 63ec27a..bcad558 100644
--- a/media/jni/android_media_MediaMetricsJNI.h
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -19,7 +19,7 @@
 
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
 #include <binder/PersistableBundle.h>
 
 // Copeid from core/jni/ (libandroid_runtime.so)
@@ -27,7 +27,7 @@
 
 class MediaMetricsJNI {
 public:
-    static jobject writeMetricsToBundle(JNIEnv* env, MediaAnalyticsItem *item, jobject mybundle);
+    static jobject writeMetricsToBundle(JNIEnv* env, mediametrics::Item *item, jobject mybundle);
     static jobject nativeToJavaPersistableBundle(JNIEnv*, os::PersistableBundle*);
 };
 
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index b4fa07b..963b650 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -23,7 +23,7 @@
 #include <media/AudioResamplerPublic.h>
 #include <media/IMediaHTTPService.h>
 #include <media/MediaPlayerInterface.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
 #include <media/stagefright/foundation/ByteUtils.h>  // for FOURCC definition
 #include <stdio.h>
 #include <assert.h>
@@ -682,7 +682,7 @@
         return (jobject) NULL;
     }
 
-    std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+    std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
     item->readFromParcel(p);
     jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
 
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index f8ba36d..6eeccf0 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -29,7 +29,7 @@
 #include <gui/Surface.h>
 #include <camera/Camera.h>
 #include <media/mediarecorder.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
 #include <media/MicrophoneInfo.h>
 #include <media/stagefright/PersistentSurface.h>
 #include <utils/threads.h>
@@ -694,7 +694,7 @@
     }
 
     // build and return the Bundle
-    std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create());
+    std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create());
     item->readFromParcel(reply);
     jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item.get(), NULL);
 
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 2c60d6b..3266285 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -16,7 +16,15 @@
 
 package com.android.mediaroutertest;
 
+import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_ALL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
+import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
+
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.media.MediaRoute2Info;
@@ -24,20 +32,37 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MediaRouter2Test {
+    private static final String TAG = "MediaRouter2Test";
     Context mContext;
+    private MediaRouter2 mRouter2;
+    private Executor mExecutor;
+
+    private static final int TIMEOUT_MS = 5000;
 
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
+        mRouter2 = MediaRouter2.getInstance(mContext);
+        mExecutor = Executors.newSingleThreadExecutor();
     }
 
     @After
@@ -50,4 +75,95 @@
         MediaRoute2Info initiallySelectedRoute = router.getSelectedRoute();
         assertNotNull(initiallySelectedRoute);
     }
+
+    /**
+     * Tests if we get proper routes for application that has special control category.
+     */
+    @Test
+    public void testGetRoutes() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
+
+        assertEquals(1, routes.size());
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+    }
+
+    @Test
+    public void testControlVolumeWithRouter() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
+
+        MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+        assertNotNull(volRoute);
+
+        int originalVolume = volRoute.getVolume();
+        int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+
+        awaitOnRouteChanged(
+                () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
+                ROUTE_ID_VARIABLE_VOLUME,
+                (route -> route.getVolume() == originalVolume + deltaVolume));
+
+        awaitOnRouteChanged(
+                () -> mRouter2.requestSetVolume(volRoute, originalVolume),
+                ROUTE_ID_VARIABLE_VOLUME,
+                (route -> route.getVolume() == originalVolume));
+    }
+
+
+    // Helper for getting routes easily
+    static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
+        Map<String, MediaRoute2Info> routeMap = new HashMap<>();
+        for (MediaRoute2Info route : routes) {
+            // intentionally not using route.getUniqueId() for convenience.
+            routeMap.put(route.getId(), route);
+        }
+        return routeMap;
+    }
+
+    Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories)
+            throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        // A dummy callback is required to send control category info.
+        MediaRouter2.Callback routerCallback = new MediaRouter2.Callback() {
+            @Override
+            public void onRoutesAdded(List<MediaRoute2Info> routes) {
+                for (int i = 0; i < routes.size(); i++) {
+                    //TODO: use isSystem() or similar method when it's ready
+                    if (!TextUtils.equals(routes.get(i).getProviderId(), SYSTEM_PROVIDER_ID)) {
+                        latch.countDown();
+                    }
+                }
+            }
+        };
+
+        mRouter2.setControlCategories(controlCategories);
+        mRouter2.registerCallback(mExecutor, routerCallback);
+        try {
+            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return createRouteMap(mRouter2.getRoutes());
+        } finally {
+            mRouter2.unregisterCallback(routerCallback);
+        }
+    }
+
+    void awaitOnRouteChanged(Runnable task, String routeId,
+            Predicate<MediaRoute2Info> predicate) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        MediaRouter2.Callback callback = new MediaRouter2.Callback() {
+            @Override
+            public void onRoutesChanged(List<MediaRoute2Info> changed) {
+                MediaRoute2Info route = createRouteMap(changed).get(routeId);
+                if (route != null && predicate.test(route)) {
+                    latch.countDown();
+                }
+            }
+        };
+        mRouter2.registerCallback(mExecutor, callback);
+        try {
+            task.run();
+            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mRouter2.unregisterCallback(callback);
+        }
+    }
 }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index c70ad8d..b380aff 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -64,6 +64,9 @@
     public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
     public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
 
+    public static final String SYSTEM_PROVIDER_ID =
+            "com.android.server.media/.SystemMediaRoute2Provider";
+
     public static final int VOLUME_MAX = 100;
     public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
     public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
@@ -78,10 +81,7 @@
     public static final String CATEGORY_SPECIAL =
             "com.android.mediarouteprovider.CATEGORY_SPECIAL";
 
-    // system routes
-    private static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
     private static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
-    private static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
 
     private static final int TIMEOUT_MS = 5000;
 
@@ -93,10 +93,9 @@
 
     private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
     private final List<MediaRouter2.Callback> mRouterCallbacks = new ArrayList<>();
-    private Map<String, MediaRoute2Info> mRoutes;
 
-    private static final List<String> CATEGORIES_ALL = new ArrayList();
-    private static final List<String> CATEGORIES_SPECIAL = new ArrayList();
+    public static final List<String> CATEGORIES_ALL = new ArrayList();
+    public static final List<String> CATEGORIES_SPECIAL = new ArrayList();
     private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>();
 
     static {
@@ -109,7 +108,6 @@
         CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO);
     }
 
-
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
@@ -118,10 +116,6 @@
         //TODO: If we need to support thread pool executors, change this to thread pool executor.
         mExecutor = Executors.newSingleThreadExecutor();
         mPackageName = mContext.getPackageName();
-
-        // ensure media router 2 client
-        addRouterCallback(new MediaRouter2.Callback());
-        mRoutes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
     }
 
     @After
@@ -168,6 +162,9 @@
     @Test
     public void testOnRoutesRemoved() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
+        addRouterCallback(new MediaRouter2.Callback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
             public void onRoutesRemoved(List<MediaRoute2Info> routes) {
@@ -182,7 +179,7 @@
 
         //TODO: Figure out a more proper way to test.
         // (Control requests shouldn't be used in this way.)
-        mRouter2.sendControlRequest(mRoutes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
+        mRouter2.sendControlRequest(routes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE));
         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
@@ -198,23 +195,15 @@
     }
 
     /**
-     * Tests if we get proper routes for application that has special control category.
-     */
-    @Test
-    public void testGetRoutes() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
-
-        assertEquals(1, routes.size());
-        assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
-    }
-
-    /**
      * Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager.
      */
     @Test
     public void testRouterOnRouteSelected() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
         CountDownLatch latch = new CountDownLatch(1);
 
+        addManagerCallback(new MediaRouter2Manager.Callback());
         addRouterCallback(new MediaRouter2.Callback() {
             @Override
             public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
@@ -224,12 +213,16 @@
             }
         });
 
-        MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
+        MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
         assertNotNull(routeToSelect);
 
-        mManager.selectRoute(mPackageName, routeToSelect);
+        try {
+            mManager.selectRoute(mPackageName, routeToSelect);
 
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mManager.unselectRoute(mPackageName);
+        }
     }
 
     /**
@@ -239,7 +232,9 @@
     @Test
     public void testManagerOnRouteSelected() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
 
+        addRouterCallback(new MediaRouter2.Callback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
             public void onRouteSelected(String packageName, MediaRoute2Info route) {
@@ -250,12 +245,15 @@
             }
         });
 
-        MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1);
+        MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
         assertNotNull(routeToSelect);
 
-        mManager.selectRoute(mPackageName, routeToSelect);
-
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        try {
+            mManager.selectRoute(mPackageName, routeToSelect);
+            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mManager.unselectRoute(mPackageName);
+        }
     }
 
     /**
@@ -263,13 +261,16 @@
      */
     @Test
     public void testSingleProviderSelect() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        addRouterCallback(new MediaRouter2.Callback());
+
         awaitOnRouteChangedManager(
-                () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID1)),
+                () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
                 ROUTE_ID1,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
 
         awaitOnRouteChangedManager(
-                () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID2)),
+                () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)),
                 ROUTE_ID2,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
 
@@ -280,27 +281,10 @@
     }
 
     @Test
-    public void testControlVolumeWithRouter() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
-
-        MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
-        int originalVolume = volRoute.getVolume();
-        int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
-
-        awaitOnRouteChanged(
-                () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
-                ROUTE_ID_VARIABLE_VOLUME,
-                (route -> route.getVolume() == originalVolume + deltaVolume));
-
-        awaitOnRouteChanged(
-                () -> mRouter2.requestSetVolume(volRoute, originalVolume),
-                ROUTE_ID_VARIABLE_VOLUME,
-                (route -> route.getVolume() == originalVolume));
-    }
-
-    @Test
     public void testControlVolumeWithManager() throws Exception {
-        MediaRoute2Info volRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+
         int originalVolume = volRoute.getVolume();
         int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
 
@@ -317,39 +301,16 @@
 
     @Test
     public void testVolumeHandling() throws Exception {
-        MediaRoute2Info fixedVolumeRoute = mRoutes.get(ROUTE_ID_FIXED_VOLUME);
-        MediaRoute2Info variableVolumeRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+
+        MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
+        MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
 
         assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling());
         assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling());
         assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
     }
 
-    @Test
-    public void testDefaultRoute() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_LIVE_AUDIO);
-
-        assertNotNull(routes.get(DEFAULT_ROUTE_ID));
-    }
-
-    Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories) throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaRouter2.Callback callback = new MediaRouter2.Callback() {
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> added) {
-                if (added.size() > 0) latch.countDown();
-            }
-        };
-        mRouter2.setControlCategories(controlCategories);
-        mRouter2.registerCallback(mExecutor, callback);
-        try {
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            return createRouteMap(mRouter2.getRoutes());
-        } finally {
-            mRouter2.unregisterCallback(callback);
-        }
-    }
-
     Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
             throws Exception {
         CountDownLatch latch = new CountDownLatch(2);
@@ -359,13 +320,17 @@
         MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
             @Override
             public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                if (routes.size() > 0) {
-                    latch.countDown();
+                for (int i = 0; i < routes.size(); i++) {
+                    //TODO: use isSystem() or similar method when it's ready
+                    if (!TextUtils.equals(routes.get(i).getProviderId(), SYSTEM_PROVIDER_ID)) {
+                        latch.countDown();
+                        break;
+                    }
                 }
             }
 
             @Override
-            public void onControlCategoriesChanged(String packageName) {
+            public void onControlCategoriesChanged(String packageName, List<String> categories) {
                 if (TextUtils.equals(mPackageName, packageName)) {
                     latch.countDown();
                 }
@@ -375,7 +340,7 @@
         mRouter2.setControlCategories(controlCategories);
         mRouter2.registerCallback(mExecutor, routerCallback);
         try {
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
             return createRouteMap(mManager.getAvailableRoutes(mPackageName));
         } finally {
             mRouter2.unregisterCallback(routerCallback);
@@ -383,27 +348,6 @@
         }
     }
 
-    void awaitOnRouteChanged(Runnable task, String routeId,
-            Predicate<MediaRoute2Info> predicate) throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaRouter2.Callback callback = new MediaRouter2.Callback() {
-            @Override
-            public void onRoutesChanged(List<MediaRoute2Info> changed) {
-                MediaRoute2Info route = createRouteMap(changed).get(routeId);
-                if (route != null && predicate.test(route)) {
-                    latch.countDown();
-                }
-            }
-        };
-        mRouter2.registerCallback(mExecutor, callback);
-        try {
-            task.run();
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mRouter2.unregisterCallback(callback);
-        }
-    }
-
     void awaitOnRouteChangedManager(Runnable task, String routeId,
             Predicate<MediaRoute2Info> predicate) throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 291cdd5..3d74868 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -131,10 +131,7 @@
     private void showDialogForInitialUser() {
         int initialUser = mCarUserManagerHelper.getInitialUser();
         UserInfo initialUserInfo = mUserManager.getUserInfo(initialUser);
-        mSelectedUser = new UserRecord(initialUserInfo,
-                /* isStartGuestSession= */ false,
-                /* isAddUser= */ false,
-                /* isForeground= */ true);
+        mSelectedUser = new UserRecord(initialUserInfo, UserRecord.FOREGROUND_USER);
 
         // If the initial user has screen lock and trusted device, display the unlock dialog on the
         // keyguard.
@@ -180,12 +177,14 @@
      */
     private void onUserSelected(UserGridRecyclerView.UserRecord record) {
         mSelectedUser = record;
-        if (hasScreenLock(record.mInfo.id) && hasTrustedDevice(record.mInfo.id)) {
-            mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
-            return;
-        }
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id);
+        if (record.mInfo != null) {
+            if (hasScreenLock(record.mInfo.id) && hasTrustedDevice(record.mInfo.id)) {
+                mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
+                return;
+            }
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id);
+            }
         }
         dismissUserSwitcher();
     }
@@ -195,7 +194,7 @@
             Log.e(TAG, "Request to dismiss user switcher, but no user selected");
             return;
         }
-        if (mSelectedUser.mIsForeground) {
+        if (mSelectedUser.mType == UserRecord.FOREGROUND_USER) {
             hide();
             mStatusBar.dismissKeyguard();
             return;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 0a5f80f..cdabeeb 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -21,6 +21,7 @@
 import static android.os.UserManager.DISALLOW_ADD_USER;
 import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
@@ -54,6 +55,8 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -142,8 +145,8 @@
             }
 
             boolean isForeground = fgUserId == userInfo.id;
-            UserRecord record = new UserRecord(userInfo, false /* isStartGuestSession */,
-                    false /* isAddUser */, isForeground);
+            UserRecord record = new UserRecord(userInfo,
+                    isForeground ? UserRecord.FOREGROUND_USER : UserRecord.BACKGROUND_USER);
             userRecords.add(record);
         }
 
@@ -160,27 +163,21 @@
 
     private UserRecord createForegroundUserRecord() {
         return new UserRecord(mUserManager.getUserInfo(ActivityManager.getCurrentUser()),
-                false /* isStartGuestSession */, false /* isAddUser */, true /* isForeground */);
+                UserRecord.FOREGROUND_USER);
     }
 
     /**
      * Create guest user record
      */
     private UserRecord createStartGuestUserRecord() {
-        UserInfo userInfo = new UserInfo();
-        userInfo.name = mContext.getString(R.string.start_guest_session);
-        return new UserRecord(userInfo, true /* isStartGuestSession */, false /* isAddUser */,
-                false /* isForeground */);
+        return new UserRecord(null /* userInfo */, UserRecord.START_GUEST);
     }
 
     /**
      * Create add user record
      */
     private UserRecord createAddUserRecord() {
-        UserInfo userInfo = new UserInfo();
-        userInfo.name = mContext.getString(R.string.car_add_user);
-        return new UserRecord(userInfo, false /* isStartGuestSession */,
-                true /* isAddUser */, false /* isForeground */);
+        return new UserRecord(null /* userInfo */, UserRecord.ADD_USER);
     }
 
     public void setUserSelectionListener(UserSelectionListener userSelectionListener) {
@@ -258,35 +255,36 @@
             UserRecord userRecord = mUsers.get(position);
             RoundedBitmapDrawable circleIcon = getCircularUserRecordIcon(userRecord);
             holder.mUserAvatarImageView.setImageDrawable(circleIcon);
-            holder.mUserNameTextView.setText(userRecord.mInfo.name);
+            holder.mUserNameTextView.setText(getUserRecordName(userRecord));
 
             holder.mView.setOnClickListener(v -> {
                 if (userRecord == null) {
                     return;
                 }
 
-                if (userRecord.mIsStartGuestSession) {
-                    notifyUserSelected(userRecord);
-                    UserInfo guest = createNewOrFindExistingGuest(mContext);
-                    if (guest != null) {
-                        mCarUserManagerHelper.switchToUser(guest);
-                    }
-                    return;
-                }
+                switch (userRecord.mType) {
+                    case UserRecord.START_GUEST:
+                        notifyUserSelected(userRecord);
+                        UserInfo guest = createNewOrFindExistingGuest(mContext);
+                        if (guest != null) {
+                            mCarUserManagerHelper.switchToUser(guest);
+                        }
+                        break;
+                    case UserRecord.ADD_USER:
+                        // If the user wants to add a user, show dialog to confirm adding a user
+                        // Disable button so it cannot be clicked multiple times
+                        mAddUserView = holder.mView;
+                        mAddUserView.setEnabled(false);
+                        mAddUserRecord = userRecord;
 
-                // If the user wants to add a user, show dialog to confirm adding a user
-                if (userRecord.mIsAddUser) {
-                    // Disable button so it cannot be clicked multiple times
-                    mAddUserView = holder.mView;
-                    mAddUserView.setEnabled(false);
-                    mAddUserRecord = userRecord;
-
-                    handleAddUserClicked();
-                    return;
+                        handleAddUserClicked();
+                        break;
+                    default:
+                        // If the user doesn't want to be a guest or add a user, switch to the user
+                        // selected
+                        notifyUserSelected(userRecord);
+                        mCarUserManagerHelper.switchToUser(userRecord.mInfo);
                 }
-                // If the user doesn't want to be a guest or add a user, switch to the user selected
-                notifyUserSelected(userRecord);
-                mCarUserManagerHelper.switchToUser(userRecord.mInfo);
             });
 
         }
@@ -372,19 +370,44 @@
         private RoundedBitmapDrawable getCircularUserRecordIcon(UserRecord userRecord) {
             Resources resources = mContext.getResources();
             RoundedBitmapDrawable circleIcon;
-            if (userRecord.mIsStartGuestSession) {
-                circleIcon = mUserIconProvider.getRoundedGuestDefaultIcon(resources);
-            } else if (userRecord.mIsAddUser) {
-                circleIcon = RoundedBitmapDrawableFactory.create(mRes, UserIcons.convertToBitmap(
-                        mContext.getDrawable(R.drawable.car_add_circle_round)));
-                circleIcon.setCircular(true);
-            } else {
-                circleIcon = mUserIconProvider.getRoundedUserIcon(userRecord.mInfo, mContext);
+            switch (userRecord.mType) {
+                case UserRecord.START_GUEST:
+                    circleIcon = mUserIconProvider.getRoundedGuestDefaultIcon(resources);
+                    break;
+                case UserRecord.ADD_USER:
+                    circleIcon = getCircularAddUserIcon();
+                    break;
+                default:
+                    circleIcon = mUserIconProvider.getRoundedUserIcon(userRecord.mInfo, mContext);
+                    break;
             }
-
             return circleIcon;
         }
 
+        private RoundedBitmapDrawable getCircularAddUserIcon() {
+            RoundedBitmapDrawable circleIcon =
+                    RoundedBitmapDrawableFactory.create(mRes, UserIcons.convertToBitmap(
+                    mContext.getDrawable(R.drawable.car_add_circle_round)));
+            circleIcon.setCircular(true);
+            return circleIcon;
+        }
+
+        private String getUserRecordName(UserRecord userRecord) {
+            String recordName;
+            switch (userRecord.mType) {
+                case UserRecord.START_GUEST:
+                    recordName = mContext.getString(R.string.start_guest_session);
+                    break;
+                case UserRecord.ADD_USER:
+                    recordName = mContext.getString(R.string.car_add_user);
+                    break;
+                default:
+                    recordName = userRecord.mInfo.name;
+                    break;
+            }
+            return recordName;
+        }
+
         /**
          * Finds the existing Guest user, or creates one if it doesn't exist.
          * @param context App context
@@ -468,18 +491,21 @@
      * guest profile, add user profile, or the foreground user.
      */
     public static final class UserRecord {
-
         public final UserInfo mInfo;
-        public final boolean mIsStartGuestSession;
-        public final boolean mIsAddUser;
-        public final boolean mIsForeground;
+        public final @UserRecordType int mType;
 
-        public UserRecord(UserInfo userInfo, boolean isStartGuestSession, boolean isAddUser,
-                boolean isForeground) {
+        public static final int START_GUEST = 0;
+        public static final int ADD_USER = 1;
+        public static final int FOREGROUND_USER = 2;
+        public static final int BACKGROUND_USER = 3;
+
+        @IntDef({START_GUEST, ADD_USER, FOREGROUND_USER, BACKGROUND_USER})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface UserRecordType{}
+
+        public UserRecord(@Nullable UserInfo userInfo, @UserRecordType int recordType) {
             mInfo = userInfo;
-            mIsStartGuestSession = isStartGuestSession;
-            mIsAddUser = isAddUser;
-            mIsForeground = isForeground;
+            mType = recordType;
         }
     }
 
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index 3258d57..2697a10 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -29,6 +29,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+
 import com.android.internal.telephony.PhoneConstants;
 
 /**
@@ -138,7 +139,7 @@
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onRegisterDefaultNetworkAvail subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
-        telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, true);
+        telephonyMgr.createForSubscriptionId(subId).reportDefaultNetworkStatus(true);
     }
 
     private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) {
@@ -146,7 +147,7 @@
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onDeregisterDefaultNetworkAvail subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
-        telephonyMgr.carrierActionReportDefaultNetworkStatus(subId, false);
+        telephonyMgr.createForSubscriptionId(subId).reportDefaultNetworkStatus(false);
     }
 
     private static void onDisableRadio(Intent intent, Context context) {
@@ -154,7 +155,7 @@
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onDisableRadio subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
-        telephonyMgr.carrierActionSetRadioEnabled(subId, !ENABLE);
+        telephonyMgr.createForSubscriptionId(subId).setRadioEnabled(!ENABLE);
     }
 
     private static void onEnableRadio(Intent intent, Context context) {
@@ -162,7 +163,7 @@
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onEnableRadio subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
-        telephonyMgr.carrierActionSetRadioEnabled(subId, ENABLE);
+        telephonyMgr.createForSubscriptionId(subId).setRadioEnabled(ENABLE);
     }
 
     private static void onShowCaptivePortalNotification(Intent intent, Context context) {
@@ -205,7 +206,7 @@
                 SubscriptionManager.getDefaultVoiceSubscriptionId());
         logd("onResetAllCarrierActions subId: " + subId);
         final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
-        telephonyMgr.carrierActionResetAll(subId);
+        telephonyMgr.createForSubscriptionId(subId).resetAllCarrierActions();
     }
 
     private static Notification getNotification(Context context, int titleId, int textId,
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ade292f..f40105d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -598,6 +598,8 @@
     <string name="bluetooth_show_devices_without_names">Show Bluetooth devices without names</string>
     <!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
     <string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
+    <!-- Setting Checkbox title for enabling Bluetooth Gabeldorsche. [CHAR LIMIT=40] -->
+    <string name="bluetooth_enable_gabeldorsche">Enable Gabeldorsche</string>
 
     <!-- UI debug setting: Select Bluetooth AVRCP Version -->
     <string name="bluetooth_select_avrcp_version_string">Bluetooth AVRCP Version</string>
@@ -696,6 +698,8 @@
     <string name="bluetooth_show_devices_without_names_summary">Bluetooth devices without names (MAC addresses only) will be displayed</string>
     <!-- Summary of checkbox for disabling Bluetooth absolute volume -->
     <string name="bluetooth_disable_absolute_volume_summary">Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control.</string>
+    <!-- Summary of checkbox for enabling Bluetooth Gabeldorche features [CHAR LIMIT=none] -->
+    <string name="bluetooth_enable_gabeldorsche_summary">Enables the Bluetooth Gabeldorche feature stack.</string>
 
     <!-- Title of checkbox setting that enables the terminal app. [CHAR LIMIT=32] -->
     <string name="enable_terminal_title">Local terminal</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index 9c896c8..bc03c34 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -164,13 +164,13 @@
         }
     }
 
-    boolean isA2dpPlaying() {
+    boolean isAudioPlaying() {
         if (mService == null) {
             return false;
         }
         List<BluetoothDevice> srcs = mService.getConnectedDevices();
         if (!srcs.isEmpty()) {
-            if (mService.isA2dpPlaying(srcs.get(0))) {
+            if (mService.isAudioPlaying(srcs.get(0))) {
                 return true;
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 8f40ab4..80b03a4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -175,7 +175,7 @@
                     return;
                 }
                 A2dpSinkProfile a2dpSink = mProfileManager.getA2dpSinkProfile();
-                if ((a2dpSink != null) && (a2dpSink.isA2dpPlaying())){
+                if ((a2dpSink != null) && (a2dpSink.isAudioPlaying())) {
                     return;
                 }
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index d91226e..3f920a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.bluetooth;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothPbap;
@@ -52,14 +53,16 @@
 
     // These callbacks run on the main thread.
     private final class PbapServiceListener
-            implements BluetoothPbap.ServiceListener {
+            implements BluetoothProfile.ServiceListener {
 
-        public void onServiceConnected(BluetoothPbap proxy) {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
             mService = (BluetoothPbap) proxy;
             mIsProfileReady=true;
         }
 
-        public void onServiceDisconnected() {
+        @Override
+        public void onServiceDisconnected(int profile) {
             mIsProfileReady=false;
         }
     }
@@ -74,7 +77,8 @@
     }
 
     PbapServerProfile(Context context) {
-        BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener());
+        BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, new PbapServiceListener(),
+                BluetoothProfile.PBAP);
     }
 
     public boolean accessProfileEnabled() {
@@ -97,13 +101,8 @@
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
-        if (mService == null) {
-            return BluetoothProfile.STATE_DISCONNECTED;
-        }
-        if (mService.isConnected(device))
-            return BluetoothProfile.STATE_CONNECTED;
-        else
-            return BluetoothProfile.STATE_DISCONNECTED;
+        if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+        return mService.getConnectionState(device);
     }
 
     public boolean isPreferred(BluetoothDevice device) {
@@ -142,7 +141,8 @@
         Log.d(TAG, "finalize()");
         if (mService != null) {
             try {
-                mService.close();
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PBAP,
+                        mService);
                 mService = null;
             }catch (Throwable t) {
                 Log.w(TAG, "Error cleaning up PBAP proxy", t);
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 104cc8f..d3315ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -235,7 +235,7 @@
         public final CharSequence contentDescription;
         public final long requestFinishTime;
 
-        private Request(String packageName, UserHandle userHandle, Drawable icon,
+        public Request(String packageName, UserHandle userHandle, Drawable icon,
                 CharSequence label, boolean isHighBattery, CharSequence contentDescription,
                 long requestFinishTime) {
             this.packageName = packageName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 443543b..b13c483 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -36,7 +36,6 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
@@ -181,9 +180,6 @@
     static final String KEY_CONFIG = "key_config";
     static final String KEY_FQDN = "key_fqdn";
     static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
-    static final String KEY_IS_CARRIER_AP = "key_is_carrier_ap";
-    static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type";
-    static final String KEY_CARRIER_NAME = "key_carrier_name";
     static final String KEY_EAPTYPE = "eap_psktype";
     static final String KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS  =
             "key_subscription_expiration_time_in_millis";
@@ -265,8 +261,6 @@
     @PasspointConfigurationVersion private int mPasspointConfigurationVersion =
             PasspointConfigurationVersion.INVALID;
 
-    private boolean mIsCarrierAp = false;
-
     private OsuProvider mOsuProvider;
 
     private String mOsuStatus;
@@ -276,12 +270,6 @@
     private boolean mIsPskSaeTransitionMode = false;
     private boolean mIsOweTransitionMode = false;
 
-    /**
-     * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
-     */
-    private int mCarrierApEapType = WifiEnterpriseConfig.Eap.NONE;
-    private String mCarrierName = null;
-
     public AccessPoint(Context context, Bundle savedState) {
         mContext = context;
 
@@ -330,15 +318,6 @@
         if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
             mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
         }
-        if (savedState.containsKey(KEY_IS_CARRIER_AP)) {
-            mIsCarrierAp = savedState.getBoolean(KEY_IS_CARRIER_AP);
-        }
-        if (savedState.containsKey(KEY_CARRIER_AP_EAP_TYPE)) {
-            mCarrierApEapType = savedState.getInt(KEY_CARRIER_AP_EAP_TYPE);
-        }
-        if (savedState.containsKey(KEY_CARRIER_NAME)) {
-            mCarrierName = savedState.getString(KEY_CARRIER_NAME);
-        }
         if (savedState.containsKey(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS)) {
             mSubscriptionExpirationTimeInMillis =
                     savedState.getLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS);
@@ -966,10 +945,6 @@
 
             mIsPskSaeTransitionMode = AccessPoint.isPskSaeTransitionMode(bestResult);
             mIsOweTransitionMode = AccessPoint.isOweTransitionMode(bestResult);
-
-            mIsCarrierAp = bestResult.isCarrierAp;
-            mCarrierApEapType = bestResult.carrierApEapType;
-            mCarrierName = bestResult.carrierName;
         }
         // Update the config SSID of a Passpoint network to that of the best RSSI
         if (isPasspoint()) {
@@ -1094,18 +1069,6 @@
         return null;
     }
 
-    public boolean isCarrierAp() {
-        return mIsCarrierAp;
-    }
-
-    public int getCarrierApEapType() {
-        return mCarrierApEapType;
-    }
-
-    public String getCarrierName() {
-        return mCarrierName;
-    }
-
     public String getSavedNetworkSummary() {
         WifiConfiguration config = mConfig;
         if (config != null) {
@@ -1181,15 +1144,9 @@
                 summary.append(mContext.getString(R.string.tap_to_sign_up));
             }
         } else if (isActive()) {
-            if (getDetailedState() == DetailedState.CONNECTED && mIsCarrierAp) {
-                // This is the active connection on a carrier AP
-                summary.append(String.format(mContext.getString(R.string.connected_via_carrier),
-                        mCarrierName));
-            } else {
-                summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(),
-                        mInfo != null && mInfo.isEphemeral(),
-                        mInfo != null ? mInfo.getAppPackageName() : null));
-            }
+            summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(),
+                    mInfo != null && mInfo.isEphemeral(),
+                    mInfo != null ? mInfo.getAppPackageName() : null));
         } else { // not active
             if (mConfig != null && mConfig.hasNoInternetAccess()) {
                 int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
@@ -1213,9 +1170,6 @@
                         summary.append(mContext.getString(R.string.wifi_disabled_generic));
                         break;
                 }
-            } else if (mIsCarrierAp) {
-                summary.append(String.format(mContext.getString(
-                        R.string.available_via_carrier), mCarrierName));
             } else if (!isReachable()) { // Wifi out of range
                 summary.append(mContext.getString(R.string.wifi_not_in_range));
             } else { // In range, not disabled.
@@ -1427,9 +1381,6 @@
         if (mProviderFriendlyName != null) {
             savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
         }
-        savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp);
-        savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType);
-        savedState.putString(KEY_CARRIER_NAME, mCarrierName);
         savedState.putLong(KEY_SUBSCRIPTION_EXPIRATION_TIME_IN_MILLIS,
                 mSubscriptionExpirationTimeInMillis);
         savedState.putInt(KEY_PASSPOINT_CONFIGURATION_VERSION, mPasspointConfigurationVersion);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 17a73ac..4a4f0e6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -56,8 +56,6 @@
     private int mSecurity = AccessPoint.SECURITY_NONE;
     private WifiConfiguration mWifiConfig;
     private WifiInfo mWifiInfo;
-    private boolean mIsCarrierAp = false;
-    private String mCarrierName = null;
 
     Context mContext;
     private ArrayList<ScanResult> mScanResults;
@@ -99,10 +97,6 @@
         }
         bundle.putInt(AccessPoint.KEY_SECURITY, mSecurity);
         bundle.putInt(AccessPoint.KEY_SPEED, mSpeed);
-        bundle.putBoolean(AccessPoint.KEY_IS_CARRIER_AP, mIsCarrierAp);
-        if (mCarrierName != null) {
-            bundle.putString(AccessPoint.KEY_CARRIER_NAME, mCarrierName);
-        }
 
         AccessPoint ap = new AccessPoint(mContext, bundle);
         ap.setRssi(mRssi);
@@ -241,16 +235,6 @@
         return this;
     }
 
-    public TestAccessPointBuilder setIsCarrierAp(boolean isCarrierAp) {
-        mIsCarrierAp = isCarrierAp;
-        return this;
-    }
-
-    public TestAccessPointBuilder setCarrierName(String carrierName) {
-        mCarrierName = carrierName;
-        return this;
-    }
-
     public TestAccessPointBuilder setScoredNetworkCache(
             ArrayList<TimestampedScoredNetwork> scoredNetworkCache) {
         mScoredNetworkCache = scoredNetworkCache;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 22f47f1..440315f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -152,6 +152,14 @@
         // TODO(b/70983952): Fill this method in
     }
 
+    /**
+     * Result of the sign-in request indecated by the WifiEntry.SIGNIN_STATUS constants
+     */
+    public void onSignInResult(int status) {
+        // TODO(b/70983952): Fill this method in
+    }
+
+
     private void updateIcon(int level) {
         if (level == -1) {
             setIcon(null);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 3818057..61cbbd3 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -40,7 +41,6 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
@@ -473,51 +473,6 @@
     }
 
     @Test
-    public void testSummaryString_showsAvaiableViaCarrier() {
-        String carrierName = "Test Carrier";
-        ScanResult result = new ScanResult();
-        result.BSSID = "00:11:22:33:44:55";
-        result.capabilities = "EAP";
-        result.isCarrierAp = true;
-        result.carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
-        result.carrierName = carrierName;
-
-        AccessPoint ap = new AccessPoint(mContext, Collections.singletonList(result));
-        assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
-                R.string.available_via_carrier), carrierName));
-        assertThat(ap.isCarrierAp()).isEqualTo(true);
-        assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.SIM);
-        assertThat(ap.getCarrierName()).isEqualTo(carrierName);
-    }
-
-    @Test
-    public void testSummaryString_showsConnectedViaCarrier() {
-        int networkId = 123;
-        int rssi = -55;
-        String carrierName = "Test Carrier";
-        WifiConfiguration config = new WifiConfiguration();
-        config.networkId = networkId;
-        WifiInfo wifiInfo = new WifiInfo();
-        wifiInfo.setNetworkId(networkId);
-        wifiInfo.setRssi(rssi);
-
-        NetworkInfo networkInfo =
-                new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
-        networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
-
-        AccessPoint ap = new TestAccessPointBuilder(mContext)
-                .setNetworkInfo(networkInfo)
-                .setNetworkId(networkId)
-                .setRssi(rssi)
-                .setWifiInfo(wifiInfo)
-                .setIsCarrierAp(true)
-                .setCarrierName(carrierName)
-                .build();
-        assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
-                R.string.connected_via_carrier), carrierName));
-    }
-
-    @Test
     public void testSummaryString_showsDisconnected() {
         AccessPoint ap = createAccessPointWithScanResultCache();
         ap.update(new WifiConfiguration());
@@ -580,31 +535,6 @@
         assertThat(ap.getSummary()).isEqualTo("Connected via Test App");
     }
 
-    @Test
-    public void testSetScanResultWithCarrierInfo() {
-        String ssid = "ssid";
-        AccessPoint ap = new TestAccessPointBuilder(mContext).setSsid(ssid).build();
-        assertThat(ap.isCarrierAp()).isEqualTo(false);
-        assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.NONE);
-        assertThat(ap.getCarrierName()).isEqualTo(null);
-
-        int carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
-        String carrierName = "Test Carrier";
-        ScanResult scanResult = new ScanResult();
-        scanResult.SSID = ssid;
-        scanResult.BSSID = "00:11:22:33:44:55";
-        scanResult.capabilities = "";
-        scanResult.isCarrierAp = true;
-        scanResult.carrierApEapType = carrierApEapType;
-        scanResult.carrierName = carrierName;
-
-
-        ap.setScanResults(Collections.singletonList(scanResult));
-        assertThat(ap.isCarrierAp()).isEqualTo(true);
-        assertThat(ap.getCarrierApEapType()).isEqualTo(carrierApEapType);
-        assertThat(ap.getCarrierName()).isEqualTo(carrierName);
-    }
-
     private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() {
         return buildScoredNetworkWithGivenBadgeCurve(mockBadgeCurve);
     }
@@ -1435,8 +1365,15 @@
      */
     @Test
     public void testOsuAccessPointSummary_showsProvisioningUpdates() {
-        AccessPoint osuAccessPoint = new AccessPoint(mContext, createOsuProvider(),
+        OsuProvider provider = createOsuProvider();
+        Context spyContext = spy(new ContextWrapper(mContext));
+        AccessPoint osuAccessPoint = new AccessPoint(spyContext, provider,
                 mScanResults);
+        Map<OsuProvider, PasspointConfiguration> osuProviderConfigMap = new HashMap<>();
+        osuProviderConfigMap.put(provider, null);
+        when(spyContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+        when(mMockWifiManager.getMatchingPasspointConfigsForOsuProviders(
+                Collections.singleton(provider))).thenReturn(osuProviderConfigMap);
 
         osuAccessPoint.setListener(mMockAccessPointListener);
 
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 21b3ba3..c9e1944 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -224,7 +224,7 @@
     <bool name="def_charging_sounds_enabled">true</bool>
 
     <!-- Default for Settings.Secure.NOTIFICATION_BUBBLES -->
-    <bool name="def_notification_bubbles">false</bool>
+    <bool name="def_notification_bubbles">true</bool>
 
     <!-- Default for Settings.Secure.AWARE_ENABLED -->
     <bool name="def_aware_enabled">false</bool>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 1f68742..987e82e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -72,5 +72,6 @@
         Settings.Global.NOTIFICATION_BUBBLES,
         Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,
         Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER,
+        Settings.Global.DEVELOPMENT_SETTINGS_ENABLED
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 3b929b9..22d843b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -142,6 +142,8 @@
         Settings.Secure.LOCK_SCREEN_WHEN_TRUST_LOST,
         Settings.Secure.SKIP_DIRECTION,
         Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+        Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+        Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
         Settings.Secure.NAVIGATION_MODE,
         Settings.Secure.SKIP_GESTURE_COUNT,
         Settings.Secure.SKIP_TOUCH_COUNT,
@@ -155,6 +157,7 @@
         Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED,
         Settings.Secure.AWARE_LOCK_ENABLED,
         Settings.Secure.AWARE_TAP_PAUSE_GESTURE_COUNT,
-        Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT
+        Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
+        Settings.Secure.PEOPLE_STRIP,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 3d278db..72923a3 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -151,5 +151,6 @@
         VALIDATORS.put(Global.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Global.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 090af98..4b10557 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -216,6 +216,10 @@
         VALIDATORS.put(Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, JSON_OBJECT_VALIDATOR);
         VALIDATORS.put(
                 Secure.NAVIGATION_MODE, new DiscreteValueValidator(new String[] {"0", "1", "2"}));
+        VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+                new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
+        VALIDATORS.put(Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
+                new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
         VALIDATORS.put(Secure.AWARE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SKIP_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.SKIP_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
@@ -234,5 +238,6 @@
         VALIDATORS.put(Secure.AWARE_LOCK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.DISPLAY_DENSITY_FORCED, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.TAP_GESTURE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.PEOPLE_STRIP, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 80077c8..016896f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2191,6 +2191,14 @@
                 Settings.Secure.NAVIGATION_MODE,
                 SecureSettingsProto.NAVIGATION_MODE);
 
+        dumpSetting(s, p,
+                Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT,
+                SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_LEFT);
+
+        dumpSetting(s, p,
+                Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT,
+                SecureSettingsProto.GestureNavigation.BACK_GESTURE_INSET_SCALE_RIGHT);
+
         final long nfcPaymentToken = p.start(SecureSettingsProto.NFC_PAYMENT);
         dumpSetting(s, p,
                 Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 93a1407..4309c80 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3264,7 +3264,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 184;
+            private static final int SETTINGS_VERSION = 185;
 
             private final int mUserId;
 
@@ -4446,20 +4446,15 @@
                 }
 
                 if (currentVersion == 182) {
-                    // Remove secure bubble settings.
+                    // Remove secure bubble settings; it's in global now.
                     getSecureSettingsLocked(userId).deleteSettingLocked("notification_bubbles");
 
-                    // Add global bubble settings.
-                    getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
-                            getContext().getResources().getBoolean(
-                                    R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
-                            true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
-
+                    // Removed. Updated NOTIFICATION_BUBBLES to be true by default, see 184.
                     currentVersion = 183;
                 }
 
                 if (currentVersion == 183) {
-                    // Version 184: Set default values for WIRELESS_CHARGING_STARTED_SOUND
+                    // Version 183: Set default values for WIRELESS_CHARGING_STARTED_SOUND
                     // and CHARGING_STARTED_SOUND
                     final SettingsState globalSettings = getGlobalSettingsLocked();
 
@@ -4500,6 +4495,18 @@
                     currentVersion = 184;
                 }
 
+                if (currentVersion == 184) {
+                    // Version 184: Reset the default for Global Settings: NOTIFICATION_BUBBLES
+                    // This is originally set in version 182, however, the default value changed
+                    // so this step is to ensure the value is updated to the correct default.
+                    getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
+                            getContext().getResources().getBoolean(
+                                    R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
+                            true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                    currentVersion = 185;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index d5a3254..19ff244 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -215,7 +215,6 @@
                     Settings.Global.DEFAULT_DNS_SERVER,
                     Settings.Global.DEFAULT_INSTALL_LOCATION,
                     Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
-                    Settings.Global.DEFAULT_USER_ID_TO_BOOT_INTO,
                     Settings.Global.DESK_DOCK_SOUND,
                     Settings.Global.DESK_UNDOCK_SOUND,
                     Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT,
@@ -223,7 +222,6 @@
                     Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
                     Settings.Global.DEVELOPMENT_FORCE_RTL,
                     Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM,
-                    Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
                     Settings.Global.DEVICE_DEMO_MODE,
                     Settings.Global.DEVICE_IDLE_CONSTANTS,
                     Settings.Global.BATTERY_SAVER_ADAPTIVE_CONSTANTS,
@@ -735,7 +733,8 @@
                  Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
                  Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
                  Settings.Secure.FACE_UNLOCK_RE_ENROLL,
-                 Settings.Secure.TAP_GESTURE);
+                 Settings.Secure.TAP_GESTURE,
+                 Settings.Secure.WINDOW_MAGNIFICATION);
 
     @Test
     public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b89f141..51bf441 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -224,6 +224,9 @@
     <!-- Permission requried for CTS test - CellBroadcastIntentsTest -->
     <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"/>
 
+    <!-- Permission required for CTS test - TetheringManagerTest -->
+    <uses-permission android:name="android.permission.TETHER_PRIVILEGED"/>
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
index 2d37b4c..15fd1f7 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneOverlayService.java
@@ -101,7 +101,7 @@
     }
 
     private Uri scanFile(@NonNull final File file) {
-        return MediaStore.scanFile(this, file);
+        return MediaStore.scanFile(getContentResolver(), file);
     }
 
     private void set(@NonNull final String name, @NonNull final Uri uri) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3514704..a8318d6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -28,11 +28,6 @@
         android:glEsVersion="0x00020000"
         android:required="true" />
 
-    <!-- SysUI must be the one to define this permission; its name is
-         referenced by the core OS. -->
-    <permission android:name="android.permission.systemui.IDENTITY"
-        android:protectionLevel="signature" />
-
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 
     <!-- Used to read wallpaper -->
diff --git a/packages/SystemUI/res/drawable/ic_control_magnification_grey.xml b/packages/SystemUI/res/drawable/ic_control_magnification_grey.xml
new file mode 100644
index 0000000..80ce8c1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_control_magnification_grey.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2019 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<vector android:height="96dp" android:viewportHeight="24"
+    android:viewportWidth="24" android:width="96dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#757575" android:pathData="M15.54,5.54L13.77,7.3 12,5.54 10.23,7.3 8.46,5.54 12,2zM18.46,15.54l-1.76,-1.77L18.46,12l-1.76,-1.77 1.76,-1.77L22,12zM8.46,18.46l1.77,-1.76L12,18.46l1.77,-1.76 1.77,1.76L12,22zM5.54,8.46l1.76,1.77L5.54,12l1.76,1.77 -1.76,1.77L2,12z"/>
+    <path android:fillColor="#757575" android:pathData="M12,12m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/magnifier_controllers.xml b/packages/SystemUI/res/layout/magnifier_controllers.xml
new file mode 100644
index 0000000..0203cd4
--- /dev/null
+++ b/packages/SystemUI/res/layout/magnifier_controllers.xml
@@ -0,0 +1,65 @@
+<?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.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/magnification_controls_size"
+    android:layout_alignParentBottom="true"
+    android:layout_alignParentEnd="true"
+    android:layout_height="@dimen/magnification_controls_size"
+    android:gravity="center">
+
+  <RelativeLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content">
+
+    <ImageView
+        android:focusable="true"
+        android:id="@+id/controller"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_control_magnification_grey" />
+
+    <View
+        android:id="@+id/left_control"
+        android:layout_width="@dimen/magnifier_left_right_controls_width"
+        android:layout_height="@dimen/magnifier_left_right_controls_height"
+        android:layout_alignLeft="@+id/controller"
+        android:layout_centerVertical="true" />
+
+    <View
+        android:id="@+id/up_control"
+        android:layout_width="@dimen/magnifier_up_down_controls_width"
+        android:layout_height="@dimen/magnifier_up_down_controls_height"
+        android:layout_alignTop="@+id/controller"
+        android:layout_centerHorizontal="true" />
+
+    <View
+        android:id="@+id/right_control"
+        android:layout_width="@dimen/magnifier_left_right_controls_width"
+        android:layout_height="@dimen/magnifier_left_right_controls_height"
+        android:layout_alignRight="@+id/controller"
+        android:layout_centerVertical="true" />
+
+    <View
+        android:id="@+id/down_control"
+        android:layout_width="@dimen/magnifier_up_down_controls_width"
+        android:layout_height="@dimen/magnifier_up_down_controls_height"
+        android:layout_alignBottom="@+id/controller"
+        android:layout_centerHorizontal="true" />
+  </RelativeLayout>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
new file mode 100644
index 0000000..f818612
--- /dev/null
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -0,0 +1,71 @@
+<?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.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <SurfaceView
+        android:layout_marginStart="@dimen/magnification_border_size"
+        android:layout_marginTop="@dimen/magnification_border_size"
+        android:id="@+id/surface_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <View
+            android:id="@+id/left_handle"
+            android:layout_width="@dimen/magnification_border_size"
+            android:layout_height="match_parent"
+            android:layout_above="@+id/drag_handle"
+            android:background="@color/magnification_border_color" />
+
+        <View
+            android:id="@+id/top_handle"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/magnification_border_size"
+            android:background="@color/magnification_border_color" />
+
+        <View
+            android:id="@+id/right_handle"
+            android:layout_width="@dimen/magnification_border_size"
+            android:layout_height="match_parent"
+            android:layout_above="@+id/drag_handle"
+            android:layout_alignParentEnd="true"
+            android:background="@color/magnification_border_color" />
+
+        <View
+            android:id="@+id/bottom_handle"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/magnification_border_size"
+            android:layout_above="@+id/drag_handle"
+            android:background="@color/magnification_border_color" />
+
+        <View
+            android:id="@+id/drag_handle"
+            android:layout_width="@dimen/magnification_drag_view_width"
+            android:layout_height="@dimen/magnification_drag_view_height"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+            android:background="@color/magnification_border_color" />
+
+    </RelativeLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 92c7477..c142465 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -211,4 +211,5 @@
     <color name="GM2_green_500">#FF34A853</color>
     <color name="GM2_blue_500">#FF4285F4</color>
 
+    <color name="magnification_border_color">#FF9900</color>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e896c16..640f31b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -295,6 +295,7 @@
         <item>com.android.systemui.SizeCompatModeActivityController</item>
         <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
         <item>com.android.systemui.theme.ThemeOverlayController</item>
+        <item>com.android.systemui.accessibility.WindowMagnification</item>
     </string-array>
 
     <!-- SystemUI vender service, used in config_systemUIServiceComponents. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c948116..da0323a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1139,4 +1139,16 @@
     <dimen name="qs_media_width">350dp</dimen>
     <dimen name="qs_media_padding">8dp</dimen>
     <dimen name="qs_media_corner_radius">10dp</dimen>
+
+    <dimen name="magnification_border_size">5dp</dimen>
+    <dimen name="magnification_frame_move_short">5dp</dimen>
+    <dimen name="magnification_frame_move_long">25dp</dimen>
+    <dimen name="magnification_drag_view_width">100dp</dimen>
+    <dimen name="magnification_drag_view_height">35dp</dimen>
+    <dimen name="magnification_controls_size">90dp</dimen>
+    <dimen name="magnifier_left_right_controls_width">35dp</dimen>
+    <dimen name="magnifier_left_right_controls_height">45dp</dimen>
+    <dimen name="magnifier_up_down_controls_width">45dp</dimen>
+    <dimen name="magnifier_up_down_controls_height">40dp</dimen>
+
 </resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index deae7e2..c1cf7b4 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -32,4 +32,6 @@
     <!-- Ratio of "right" end of status bar that will swipe to QS. -->
     <integer name="qs_split_fraction">2</integer>
 
+    <integer name="magnification_default_scale">2</integer>
+
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1053750..45318fd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -914,8 +914,8 @@
     <string name="quick_settings_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string>
     <!-- QuickSettings: Label for the toggle to activate dark theme (A.K.A Dark Mode). [CHAR LIMIT=20] -->
     <string name="quick_settings_ui_mode_night_label">Dark theme</string>
-    <!-- QuickSettings: Label for the dark theme tile when enabled by battery saver. [CHAR LIMIT=40] -->
-    <string name="quick_settings_ui_mode_night_label_battery_saver">Dark theme\nBattery saver</string>
+    <!-- QuickSettings: Secondary text for the dark theme tile when enabled by battery saver. [CHAR LIMIT=20] -->
+    <string name="quick_settings_dark_mode_secondary_label_battery_saver">Battery Saver</string>
     <!-- QuickSettings: Secondary text for when the Dark Mode will be enabled at sunset. [CHAR LIMIT=20] -->
     <string name="quick_settings_dark_mode_secondary_label_on_at_sunset">On at sunset</string>
     <!-- QuickSettings: Secondary text for when the Dark Mode will be on until sunrise. [CHAR LIMIT=20] -->
@@ -2492,4 +2492,12 @@
 
     <!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] -->
     <string name="inattentive_sleep_warning_title">Standby</string>
+
+    <!-- Window Magnification strings -->
+    <!-- Title for Magnification Overlay Window [CHAR LIMIT=NONE] -->
+    <string name="magnification_overlay_title">Magnification Overlay Window</string>
+    <!-- Title for Magnification Window [CHAR LIMIT=NONE] -->
+    <string name="magnification_window_title">Magnification Window</string>
+    <!-- Title for Magnification Controls Window [CHAR LIMIT=NONE] -->
+    <string name="magnification_controls_title">Magnification Window Controls</string>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 22d1675c..9f13718 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -27,6 +27,7 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
@@ -215,4 +216,23 @@
     public void removePinnedStackListener(PinnedStackListener listener) {
         mPinnedStackListenerForwarder.removeListener(listener);
     }
+
+    /**
+     * Mirrors a specified display. The SurfaceControl returned is the root of the mirrored
+     * hierarchy.
+     *
+     * @param displayId The id of the display to mirror
+     * @return The SurfaceControl for the root of the mirrored hierarchy.
+     */
+    public SurfaceControl mirrorDisplay(final int displayId) {
+        try {
+            SurfaceControl outSurfaceControl = new SurfaceControl();
+            WindowManagerGlobal.getWindowManagerService().mirrorDisplay(displayId,
+                    outSurfaceControl);
+            return outSurfaceControl;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to reach window manager", e);
+        }
+        return null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 5d084e7..c2cedad 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -30,6 +30,7 @@
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Slog;
 import android.view.MotionEvent;
 import android.view.View;
@@ -114,11 +115,7 @@
         super.onFinishInflate();
         mLockPatternUtils = new LockPatternUtils(mContext);
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        setOnClickListener(new OnClickListener() {
-            public void onClick(View v) {
-                takeEmergencyCallAction();
-            }
-        });
+        setOnClickListener(v -> takeEmergencyCallAction());
         setOnLongClickListener(new OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
@@ -168,9 +165,9 @@
      */
     public void takeEmergencyCallAction() {
         MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_CALL);
-        // TODO: implement a shorter timeout once new PowerManager API is ready.
-        // should be the equivalent to the old userActivity(EMERGENCY_CALL_TIMEOUT)
-        mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+        if (mPowerManager != null) {
+            mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
+        }
         try {
             ActivityTaskManager.getService().stopSystemLockTaskMode();
         } catch (RemoteException e) {
@@ -182,10 +179,19 @@
                 mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
             }
         } else {
-            Dependency.get(KeyguardUpdateMonitor.class).reportEmergencyCallAction(
-                    true /* bypassHandler */);
+            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+            if (updateMonitor != null) {
+                updateMonitor.reportEmergencyCallAction(true /* bypassHandler */);
+            } else {
+                Log.w(LOG_TAG, "KeyguardUpdateMonitor was null, launching intent anyway.");
+            }
+            TelecomManager telecomManager = getTelecommManager();
+            if (telecomManager == null) {
+                Log.wtf(LOG_TAG, "TelecomManager was null, cannot launch emergency dialer");
+                return;
+            }
             Intent emergencyDialIntent =
-                    getTelecommManager().createLaunchEmergencyDialerIntent(null /* number*/)
+                    telecomManager.createLaunchEmergencyDialerIntent(null /* number*/)
                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                                     | Intent.FLAG_ACTIVITY_CLEAR_TOP)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
new file mode 100644
index 0000000..6178ff2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
+
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.qualifiers.MainHandler;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Class to handle changes to setting window_magnification value.
+ */
+@Singleton
+public class WindowMagnification extends SystemUI {
+    private WindowMagnificationController mWindowMagnificationController;
+    private final Handler mHandler;
+
+    @Inject
+    public WindowMagnification(Context context, @MainHandler Handler mainHandler) {
+        super(context);
+        mHandler = mainHandler;
+    }
+
+    @Override
+    public void start() {
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.WINDOW_MAGNIFICATION),
+                true, new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        updateWindowMagnification();
+                    }
+                });
+    }
+
+    private void updateWindowMagnification() {
+        try {
+            boolean enable = Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.WINDOW_MAGNIFICATION) != 0;
+            if (enable) {
+                enableMagnification();
+            } else {
+                disableMagnification();
+            }
+        } catch (Settings.SettingNotFoundException e) {
+            disableMagnification();
+        }
+    }
+
+    private void enableMagnification() {
+        if (mWindowMagnificationController == null) {
+            mWindowMagnificationController = new WindowMagnificationController(mContext, mHandler);
+        }
+        mWindowMagnificationController.createWindowMagnification();
+    }
+
+    private void disableMagnification() {
+        if (mWindowMagnificationController != null) {
+            mWindowMagnificationController.deleteWindowMagnification();
+        }
+        mWindowMagnificationController = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
new file mode 100644
index 0000000..e3694ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -0,0 +1,424 @@
+/*
+ * 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.accessibility;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Handler;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+/**
+ * Class to handle adding and removing a window magnification.
+ */
+public class WindowMagnificationController implements View.OnClickListener,
+        View.OnLongClickListener, View.OnTouchListener, SurfaceHolder.Callback {
+    private final int mBorderSize;
+    private final int mMoveFrameAmountShort;
+    private final int mMoveFrameAmountLong;
+
+    private final Context mContext;
+    private final Point mDisplaySize = new Point();
+    private final int mDisplayId;
+    private final Handler mHandler;
+    private final Rect mMagnificationFrame = new Rect();
+    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private final WindowManager mWm;
+
+    private float mScale;
+
+    private final Rect mTmpRect = new Rect();
+
+    // The root of the mirrored content
+    private SurfaceControl mMirrorSurface;
+
+    private boolean mIsPressedDown;
+
+    private View mLeftControl;
+    private View mUpControl;
+    private View mRightControl;
+    private View mBottomControl;
+
+    private View mDragView;
+    private View mLeftDrag;
+    private View mTopDrag;
+    private View mRightDrag;
+    private View mBottomDrag;
+
+    private final PointF mLastDrag = new PointF();
+    private final Point mMoveWindowOffset = new Point();
+
+    private View mMirrorView;
+    private SurfaceView mMirrorSurfaceView;
+    private View mControlsView;
+    private View mOverlayView;
+
+    private MoveMirrorRunnable mMoveMirrorRunnable = new MoveMirrorRunnable();
+
+    WindowMagnificationController(Context context, Handler handler) {
+        mContext = context;
+        mHandler = handler;
+        Display display = mContext.getDisplay();
+        display.getSize(mDisplaySize);
+        mDisplayId = mContext.getDisplayId();
+
+        mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+        Resources r = context.getResources();
+        mBorderSize = (int) r.getDimension(R.dimen.magnification_border_size);
+        mMoveFrameAmountShort = (int) r.getDimension(R.dimen.magnification_frame_move_short);
+        mMoveFrameAmountLong = (int) r.getDimension(R.dimen.magnification_frame_move_long);
+
+        mScale = r.getInteger(R.integer.magnification_default_scale);
+    }
+
+    /**
+     * Creates a magnification window if it doesn't already exist.
+     */
+    void createWindowMagnification() {
+        if (mMirrorView != null) {
+            return;
+        }
+        createOverlayWindow();
+    }
+
+    private void createOverlayWindow() {
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSPARENT);
+        params.gravity = Gravity.TOP | Gravity.LEFT;
+        params.token = new Binder();
+        params.setTitle(mContext.getString(R.string.magnification_overlay_title));
+
+        mOverlayView = new View(mContext);
+        mOverlayView.getViewTreeObserver().addOnWindowAttachListener(
+                new ViewTreeObserver.OnWindowAttachListener() {
+                    @Override
+                    public void onWindowAttached() {
+                        mOverlayView.getViewTreeObserver().removeOnWindowAttachListener(this);
+                        createMirrorWindow();
+                        createControls();
+                    }
+
+                    @Override
+                    public void onWindowDetached() {
+
+                    }
+                });
+
+        mOverlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+
+        mWm.addView(mOverlayView, params);
+    }
+
+    /**
+     * Deletes the magnification window.
+     */
+    void deleteWindowMagnification() {
+        if (mMirrorSurface != null) {
+            mTransaction.remove(mMirrorSurface).apply();
+            mMirrorSurface = null;
+        }
+
+        if (mOverlayView != null) {
+            mWm.removeView(mOverlayView);
+            mOverlayView = null;
+        }
+
+        if (mMirrorView != null) {
+            mWm.removeView(mMirrorView);
+            mMirrorView = null;
+        }
+
+        if (mControlsView != null) {
+            mWm.removeView(mControlsView);
+            mControlsView = null;
+        }
+    }
+
+    private void createMirrorWindow() {
+        setInitialStartBounds();
+
+        // The window should be the size the mirrored surface will be but also add room for the
+        // border and the drag handle.
+        int dragViewHeight = (int) mContext.getResources().getDimension(
+                R.dimen.magnification_drag_view_height);
+        int windowWidth = mMagnificationFrame.width() + 2 * mBorderSize;
+        int windowHeight = mMagnificationFrame.height() + dragViewHeight + 2 * mBorderSize;
+
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                windowWidth, windowHeight,
+                WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,
+                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.TRANSPARENT);
+        params.gravity = Gravity.TOP | Gravity.LEFT;
+        params.token = mOverlayView.getWindowToken();
+        params.x = mMagnificationFrame.left;
+        params.y = mMagnificationFrame.top;
+        params.setTitle(mContext.getString(R.string.magnification_window_title));
+
+        mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
+        mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
+        // This places the SurfaceView's SurfaceControl above the ViewRootImpl's SurfaceControl to
+        // ensure the mirrored area can get touch instead of going to the window
+        mMirrorSurfaceView.setZOrderOnTop(true);
+
+        mMirrorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+        mWm.addView(mMirrorView, params);
+
+        SurfaceHolder holder = mMirrorSurfaceView.getHolder();
+        holder.addCallback(this);
+        holder.setFormat(PixelFormat.RGBA_8888);
+
+        addDragTouchListeners();
+    }
+
+    private void createControls() {
+        int controlsSize = (int) mContext.getResources().getDimension(
+                R.dimen.magnification_controls_size);
+
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(controlsSize, controlsSize,
+                WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
+                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                PixelFormat.RGBA_8888);
+        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+        lp.token = mOverlayView.getWindowToken();
+        lp.setTitle(mContext.getString(R.string.magnification_controls_title));
+
+        mControlsView = LayoutInflater.from(mContext).inflate(R.layout.magnifier_controllers, null);
+        mWm.addView(mControlsView, lp);
+
+        mLeftControl = mControlsView.findViewById(R.id.left_control);
+        mUpControl = mControlsView.findViewById(R.id.up_control);
+        mRightControl = mControlsView.findViewById(R.id.right_control);
+        mBottomControl = mControlsView.findViewById(R.id.down_control);
+
+        mLeftControl.setOnClickListener(this);
+        mUpControl.setOnClickListener(this);
+        mRightControl.setOnClickListener(this);
+        mBottomControl.setOnClickListener(this);
+
+        mLeftControl.setOnLongClickListener(this);
+        mUpControl.setOnLongClickListener(this);
+        mRightControl.setOnLongClickListener(this);
+        mBottomControl.setOnLongClickListener(this);
+
+        mLeftControl.setOnTouchListener(this);
+        mUpControl.setOnTouchListener(this);
+        mRightControl.setOnTouchListener(this);
+        mBottomControl.setOnTouchListener(this);
+    }
+
+    private void setInitialStartBounds() {
+        // Sets the initial frame area for the mirror and places it in the center of the display.
+        int initSize = Math.min(mDisplaySize.x, mDisplaySize.y) / 2;
+        int initX = mDisplaySize.x / 2 - initSize / 2;
+        int initY = mDisplaySize.y / 2 - initSize / 2;
+        mMagnificationFrame.set(initX, initY, initX + initSize, initY + initSize);
+    }
+
+    /**
+     * This is called once the surfaceView is created so the mirrored content can be placed as a
+     * child of the surfaceView.
+     */
+    private void createMirror() {
+        mMirrorSurface = WindowManagerWrapper.getInstance().mirrorDisplay(mDisplayId);
+        if (!mMirrorSurface.isValid()) {
+            return;
+        }
+        mTransaction.show(mMirrorSurface)
+                .reparent(mMirrorSurface, mMirrorSurfaceView.getSurfaceControl());
+
+        modifyWindowMagnification(mTransaction);
+        mTransaction.apply();
+    }
+
+    private void addDragTouchListeners() {
+        mDragView = mMirrorView.findViewById(R.id.drag_handle);
+        mLeftDrag = mMirrorView.findViewById(R.id.left_handle);
+        mTopDrag = mMirrorView.findViewById(R.id.top_handle);
+        mRightDrag = mMirrorView.findViewById(R.id.right_handle);
+        mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
+
+        mDragView.setOnTouchListener(this);
+        mLeftDrag.setOnTouchListener(this);
+        mTopDrag.setOnTouchListener(this);
+        mRightDrag.setOnTouchListener(this);
+        mBottomDrag.setOnTouchListener(this);
+    }
+
+    /**
+     * Modifies the placement of the mirrored content.
+     */
+    private void modifyWindowMagnification(SurfaceControl.Transaction t) {
+        Rect sourceBounds = getSourceBounds(mMagnificationFrame, mScale);
+        // The final destination for the magnification surface should be at 0,0 since the
+        // ViewRootImpl's position will change
+        mTmpRect.set(0, 0, mMagnificationFrame.width(), mMagnificationFrame.height());
+
+        WindowManager.LayoutParams params =
+                (WindowManager.LayoutParams) mMirrorView.getLayoutParams();
+        params.x = mMagnificationFrame.left;
+        params.y = mMagnificationFrame.top;
+        mWm.updateViewLayout(mMirrorView, params);
+
+        t.setGeometry(mMirrorSurface, sourceBounds, mTmpRect, Surface.ROTATION_0);
+    }
+
+    @Override
+    public void onClick(View v) {
+        setMoveOffset(v, mMoveFrameAmountShort);
+        moveMirrorFromControls();
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        mIsPressedDown = true;
+        setMoveOffset(v, mMoveFrameAmountLong);
+        mHandler.post(mMoveMirrorRunnable);
+        return true;
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        if (v == mLeftControl || v == mUpControl || v == mRightControl || v == mBottomControl) {
+            return handleControlTouchEvent(event);
+        } else if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
+                || v == mBottomDrag) {
+            return handleDragTouchEvent(event);
+        }
+        return false;
+    }
+
+    private boolean handleControlTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mIsPressedDown = false;
+                break;
+        }
+        return false;
+    }
+
+    private boolean handleDragTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mLastDrag.set(event.getRawX(), event.getRawY());
+                return true;
+            case MotionEvent.ACTION_MOVE:
+                int xDiff = (int) (event.getRawX() - mLastDrag.x);
+                int yDiff = (int) (event.getRawY() - mLastDrag.y);
+                mMagnificationFrame.offset(xDiff, yDiff);
+                mLastDrag.set(event.getRawX(), event.getRawY());
+                modifyWindowMagnification(mTransaction);
+                mTransaction.apply();
+                return true;
+        }
+        return false;
+    }
+
+    private void setMoveOffset(View v, int moveFrameAmount) {
+        mMoveWindowOffset.set(0, 0);
+
+        if (v == mLeftControl) {
+            mMoveWindowOffset.x = -moveFrameAmount;
+        } else if (v == mUpControl) {
+            mMoveWindowOffset.y = -moveFrameAmount;
+        } else if (v == mRightControl) {
+            mMoveWindowOffset.x = moveFrameAmount;
+        } else if (v == mBottomControl) {
+            mMoveWindowOffset.y = moveFrameAmount;
+        }
+    }
+
+    private void moveMirrorFromControls() {
+        mMagnificationFrame.offset(mMoveWindowOffset.x, mMoveWindowOffset.y);
+
+        modifyWindowMagnification(mTransaction);
+        mTransaction.apply();
+    }
+
+    /**
+     * Calculates the desired source bounds. This will be the area under from the center of  the
+     * displayFrame, factoring in scale.
+     */
+    private Rect getSourceBounds(Rect displayFrame, float scale) {
+        int halfWidth = displayFrame.width() / 2;
+        int halfHeight = displayFrame.height() / 2;
+        int left = displayFrame.left + (halfWidth - (int) (halfWidth / scale));
+        int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale));
+        int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale));
+        int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale));
+        return new Rect(left, top, right, bottom);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        createMirror();
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+    }
+
+    class MoveMirrorRunnable implements Runnable {
+        @Override
+        public void run() {
+            if (mIsPressedDown) {
+                moveMirrorFromControls();
+                mHandler.postDelayed(mMoveMirrorRunnable, 100);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 6b0d3c8..e0ca1ac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -18,6 +18,7 @@
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -29,7 +30,6 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.hardware.biometrics.Authenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
@@ -341,6 +341,10 @@
         if (DEBUG) Log.d(TAG, "hideAuthenticationDialog");
 
         mCurrentDialog.dismissFromSystemServer();
+
+        // BiometricService will have already sent the callback to the client in this case.
+        // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done.
+        mCurrentDialog = null;
     }
 
     private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
@@ -416,7 +420,7 @@
                     // TODO: Clean this up
                     Bundle bundle = (Bundle) mCurrentDialogArgs.arg1;
                     bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
-                            Authenticator.TYPE_CREDENTIAL);
+                            Authenticators.DEVICE_CREDENTIAL);
                 }
 
                 showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
new file mode 100644
index 0000000..8765c9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+kchyn@google.com
+jaggies@google.com
+curtislb@google.com
+ilyamaty@google.com
+joshmccloskey@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
index d6f830d..7d237c4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -16,23 +16,21 @@
 
 package com.android.systemui.biometrics;
 
+import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
 
 import android.annotation.IntDef;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
-import android.hardware.biometrics.Authenticator;
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.util.DisplayMetrics;
-import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.R;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -71,12 +69,12 @@
 
     static boolean isDeviceCredentialAllowed(Bundle biometricPromptBundle) {
         final int authenticators = getAuthenticators(biometricPromptBundle);
-        return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0;
+        return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
     }
 
     static boolean isBiometricAllowed(Bundle biometricPromptBundle) {
         final int authenticators = getAuthenticators(biometricPromptBundle);
-        return (authenticators & Authenticator.TYPE_BIOMETRIC) != 0;
+        return (authenticators & Authenticators.BIOMETRIC_WEAK) != 0;
     }
 
     static int getAuthenticators(Bundle biometricPromptBundle) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 1277736..c82bc30 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -103,7 +103,6 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -735,7 +734,7 @@
     @SuppressWarnings("FieldCanBeLocal")
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
-        public void onPendingEntryAdded(NotificationEntry entry) {
+        public void onNotificationAdded(NotificationEntry entry) {
             boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
             boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
             boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 21471ec..4c1cf49 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -32,6 +32,9 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -39,8 +42,11 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import com.android.internal.graphics.ColorUtils;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleHubNotificationListenerKt;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -227,15 +233,29 @@
         List<Person> personList = getPeopleFromNotification(entry);
         if (personList.size() > 0) {
             final Person person = personList.get(0);
-
             if (person != null) {
                 icon = person.getIcon();
+                if (icon == null) {
+                    // Lets try and grab the icon constructed by the layout
+                    Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
+                    if (d instanceof  BitmapDrawable) {
+                        icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+                    }
+                }
             }
         }
         if (icon == null) {
-            icon = notification.getLargeIcon() != null
-                    ? notification.getLargeIcon()
-                    : notification.getSmallIcon();
+            boolean shouldTint = notification.getLargeIcon() == null;
+            icon = shouldTint
+                    ? notification.getSmallIcon()
+                    : notification.getLargeIcon();
+            if (shouldTint) {
+                int notifColor = entry.getSbn().getNotification().color;
+                notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
+                notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
+                        true /* findFg */, 3f);
+                icon.setTint(notifColor);
+            }
         }
         if (intent != null) {
             return new Notification.BubbleMetadata.Builder()
@@ -285,7 +305,7 @@
     }
 
     static boolean isShortcutIntent(PendingIntent intent) {
-        return intent.equals(sDummyShortcutIntent);
+        return intent != null && intent.equals(sDummyShortcutIntent);
     }
 
     static List<Person> getPeopleFromNotification(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index f032a33..534f350 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -23,6 +23,7 @@
 import android.app.IActivityManager;
 import android.app.IWallpaperManager;
 import android.app.KeyguardManager;
+import android.app.NotificationManager;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
@@ -131,6 +132,12 @@
 
     @Singleton
     @Provides
+    static NotificationManager provideNotificationManager(Context context) {
+        return context.getSystemService(NotificationManager.class);
+    }
+
+    @Singleton
+    @Provides
     static PackageManagerWrapper providePackageManagerWrapper() {
         return PackageManagerWrapper.getInstance();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 3cf14d6..99dd5e2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -21,6 +21,7 @@
 import com.android.systemui.SizeCompatModeActivityController;
 import com.android.systemui.SliceBroadcastRelayHandler;
 import com.android.systemui.SystemUI;
+import com.android.systemui.accessibility.WindowMagnification;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -156,4 +157,10 @@
     @IntoMap
     @ClassKey(VolumeUI.class)
     public abstract SystemUI bindVolumeUI(VolumeUI sysui);
+
+    /** Inject into WindowMagnification. */
+    @Binds
+    @IntoMap
+    @ClassKey(WindowMagnification.class)
+    public abstract SystemUI bindWindowMagnification(WindowMagnification sysui);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 05a234f..d008e66 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -80,8 +80,7 @@
 
     public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
             DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
-            Callback callback, Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy,
-            DozeLog dozeLog) {
+            Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) {
         mContext = context;
         mAlarmManager = alarmManager;
         mSensorManager = sensorManager;
@@ -154,7 +153,7 @@
         };
 
         mProximitySensor = new ProximitySensor(context.getResources(), sensorManager);
-
+        setProxListening(false);  // Don't immediately start listening when we register.
         mProximitySensor.register(
                 proximityEvent -> {
                     if (proximityEvent != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 2d6b946..722dc03 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -103,8 +103,7 @@
         mWakeLock = wakeLock;
         mAllowPulseTriggers = allowPulseTriggers;
         mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
-                config, wakeLock, this::onSensor, this::onProximityFar,
-                dozeParameters.getPolicy(), dozeLog);
+                config, wakeLock, this::onSensor, this::onProximityFar, dozeLog);
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mDockManager = dockManager;
         mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 7f1b356..7aeb785 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.doze;
 
+import android.annotation.Nullable;
 import android.app.IWallpaperManager;
 import android.os.RemoteException;
 import android.util.Log;
@@ -34,6 +35,7 @@
     private static final String TAG = "DozeWallpaperState";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    @Nullable
     private final IWallpaperManager mWallpaperManagerService;
     private final DozeParameters mDozeParameters;
     private final BiometricUnlockController mBiometricUnlockController;
@@ -79,16 +81,18 @@
 
         if (isAmbientMode != mIsAmbientMode) {
             mIsAmbientMode = isAmbientMode;
-            try {
-                long duration = animated ? StackStateAnimator.ANIMATION_DURATION_WAKEUP : 0L;
-                if (DEBUG) {
-                    Log.i(TAG, "AOD wallpaper state changed to: " + mIsAmbientMode
+            if (mWallpaperManagerService != null) {
+                try {
+                    long duration = animated ? StackStateAnimator.ANIMATION_DURATION_WAKEUP : 0L;
+                    if (DEBUG) {
+                        Log.i(TAG, "AOD wallpaper state changed to: " + mIsAmbientMode
                             + ", animationDuration: " + duration);
+                    }
+                    mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, duration);
+                } catch (RemoteException e) {
+                    // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
+                    Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
                 }
-                mWallpaperManagerService.setInAmbientMode(mIsAmbientMode, duration);
-            } catch (RemoteException e) {
-                // Cannot notify wallpaper manager service, but it's fine, let's just skip it.
-                Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode);
             }
         }
     }
@@ -97,5 +101,6 @@
     public void dump(PrintWriter pw) {
         pw.println("DozeWallpaperState:");
         pw.println(" isAmbientMode: " + mIsAmbientMode);
+        pw.println(" hasWallpaperService: " + (mWallpaperManagerService != null));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index d79e383..7bc2a0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -92,7 +92,10 @@
         boolean nightMode = (mContext.getResources().getConfiguration().uiMode
                         & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
 
-        if (isAuto && !powerSave) {
+        if (powerSave) {
+            state.secondaryLabel = mContext.getResources().getString(
+                    R.string.quick_settings_dark_mode_secondary_label_battery_saver);
+        } else if (isAuto) {
             state.secondaryLabel = mContext.getResources().getString(nightMode
                     ? R.string.quick_settings_dark_mode_secondary_label_until_sunrise
                     : R.string.quick_settings_dark_mode_secondary_label_on_at_sunset);
@@ -100,9 +103,7 @@
             state.secondaryLabel = null;
         }
         state.value = nightMode;
-        state.label = mContext.getString(powerSave
-                ? R.string.quick_settings_ui_mode_night_label_battery_saver
-                : R.string.quick_settings_ui_mode_night_label);
+        state.label = mContext.getString(R.string.quick_settings_ui_mode_night_label);
         state.icon = mIcon;
         state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
                 ? state.label
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 0383dee..d3ccbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -371,7 +371,8 @@
         Bitmap thumbnailBitmap = null;
         try {
             ContentResolver resolver = getContentResolver();
-            Size size = Point.convert(MediaStore.ThumbnailConstants.MINI_SIZE);
+            DisplayMetrics metrics = getResources().getDisplayMetrics();
+            Size size = new Size(metrics.widthPixels, metrics.heightPixels / 2);
             thumbnailBitmap = resolver.loadThumbnail(uri, size, null);
         } catch (IOException e) {
             Log.e(TAG, "Error creating thumbnail: " + e.getMessage());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 76925b4..2f401e5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -23,6 +23,8 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
@@ -48,7 +50,9 @@
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -275,6 +279,7 @@
         Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
 
         Context context = mParams.context;
+        ContentResolver resolver = context.getContentResolver();
         Bitmap image = mParams.image;
         Resources r = context.getResources();
 
@@ -284,23 +289,27 @@
                             mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
 
             // Save the screenshot to the MediaStore
-            final MediaStore.PendingParams params = new MediaStore.PendingParams(
-                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
-            params.setRelativePath(Environment.DIRECTORY_PICTURES + File.separator
-                    + Environment.DIRECTORY_SCREENSHOTS);
+            final ContentValues values = new ContentValues();
+            values.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
+                    + File.separator + Environment.DIRECTORY_SCREENSHOTS);
+            values.put(MediaColumns.DISPLAY_NAME, mImageFileName);
+            values.put(MediaColumns.MIME_TYPE, "image/png");
+            values.put(MediaColumns.DATE_ADDED, mImageTime / 1000);
+            values.put(MediaColumns.DATE_MODIFIED, mImageTime / 1000);
+            values.put(MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
+            values.put(MediaColumns.IS_PENDING, 1);
 
-            final Uri uri = MediaStore.createPending(context, params);
-            final MediaStore.PendingSession session = MediaStore.openPending(context, uri);
+            final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
             try {
                 // First, write the actual data for our screenshot
-                try (OutputStream out = session.openOutputStream()) {
+                try (OutputStream out = resolver.openOutputStream(uri)) {
                     if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
                         throw new IOException("Failed to compress");
                     }
                 }
 
                 // Next, write metadata to help index the screenshot
-                try (ParcelFileDescriptor pfd = session.open()) {
+                try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) {
                     final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
 
                     exif.setAttribute(ExifInterface.TAG_SOFTWARE,
@@ -327,12 +336,15 @@
 
                     exif.saveAttributes();
                 }
-                session.publish();
+
+                // Everything went well above, publish it!
+                values.clear();
+                values.put(MediaColumns.IS_PENDING, 0);
+                values.putNull(MediaColumns.DATE_EXPIRES);
+                resolver.update(uri, values, null, null);
             } catch (Exception e) {
-                session.abandon();
+                resolver.delete(uri, null);
                 throw e;
-            } finally {
-                IoUtils.closeQuietly(session);
             }
 
             populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 9f79785..077d260 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.UserHandle;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -97,7 +98,7 @@
             if (!mReceiverRegistered) {
                 mCurrentUserId = ActivityManager.getCurrentUser();
                 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-                mBroadcastDispatcher.registerReceiver(this, filter);
+                mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
                 mReceiverRegistered = true;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 9a64b30..97dd3da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -19,9 +19,7 @@
 import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput;
 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
-import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
 
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.NotificationManager;
 import android.content.ComponentName;
@@ -33,8 +31,6 @@
 import android.util.Log;
 
 import com.android.systemui.dagger.qualifiers.MainHandler;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 
 import java.util.ArrayList;
@@ -52,33 +48,35 @@
 public class NotificationListener extends NotificationListenerWithPlugins {
     private static final String TAG = "NotificationListener";
 
-    // Dependencies:
-    private final NotificationEntryManager mEntryManager;
-    private final NotificationGroupManager mGroupManager;
-
     private final Context mContext;
+    private final NotificationManager mNotificationManager;
     private final Handler mMainHandler;
+    private final List<NotifServiceListener> mNotificationListeners = new ArrayList<>();
     private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
-    @Nullable private NotifServiceListener mDownstreamListener;
 
     @Inject
-    public NotificationListener(Context context, @MainHandler Handler mainHandler,
-            NotificationEntryManager notificationEntryManager,
-            NotificationGroupManager notificationGroupManager) {
+    public NotificationListener(
+            Context context,
+            NotificationManager notificationManager,
+            @MainHandler Handler mainHandler) {
         mContext = context;
+        mNotificationManager = notificationManager;
         mMainHandler = mainHandler;
-        mEntryManager = notificationEntryManager;
-        mGroupManager = notificationGroupManager;
     }
 
+    /** Registers a listener that's notified when notifications are added/removed/etc. */
+    public void addNotificationListener(NotifServiceListener listener) {
+        if (mNotificationListeners.contains(listener)) {
+            throw new IllegalArgumentException("Listener is already added");
+        }
+        mNotificationListeners.add(listener);
+    }
+
+    /** Registers a listener that's notified when any notification-related settings change. */
     public void addNotificationSettingsListener(NotificationSettingsListener listener) {
         mSettingsListeners.add(listener);
     }
 
-    public void setDownstreamListener(NotifServiceListener downstreamListener) {
-        mDownstreamListener = downstreamListener;
-    }
-
     @Override
     public void onListenerConnected() {
         if (DEBUG) Log.d(TAG, "onListenerConnected");
@@ -102,14 +100,13 @@
             final RankingMap completeMap = new RankingMap(newRankings.toArray(new Ranking[0]));
 
             for (StatusBarNotification sbn : notifications) {
-                if (mDownstreamListener != null) {
-                    mDownstreamListener.onNotificationPosted(sbn, completeMap);
+                for (NotifServiceListener listener : mNotificationListeners) {
+                    listener.onNotificationPosted(sbn, completeMap);
                 }
-                mEntryManager.addNotification(sbn, completeMap);
             }
         });
-        NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
-        onSilentStatusBarIconsVisibilityChanged(noMan.shouldHideSilentStatusBarIcons());
+        onSilentStatusBarIconsVisibilityChanged(
+                mNotificationManager.shouldHideSilentStatusBarIcons());
     }
 
     @Override
@@ -120,34 +117,8 @@
             mMainHandler.post(() -> {
                 processForRemoteInput(sbn.getNotification(), mContext);
 
-                if (mDownstreamListener != null) {
-                    mDownstreamListener.onNotificationPosted(sbn, rankingMap);
-                }
-
-                String key = sbn.getKey();
-                boolean isUpdate = mEntryManager.getActiveNotificationUnfiltered(key) != null;
-                // In case we don't allow child notifications, we ignore children of
-                // notifications that have a summary, since` we're not going to show them
-                // anyway. This is true also when the summary is canceled,
-                // because children are automatically canceled by NoMan in that case.
-                if (!ENABLE_CHILD_NOTIFICATIONS
-                        && mGroupManager.isChildInGroupWithSummary(sbn)) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
-                    }
-
-                    // Remove existing notification to avoid stale data.
-                    if (isUpdate) {
-                        mEntryManager.removeNotification(key, rankingMap, UNDEFINED_DISMISS_REASON);
-                    } else {
-                        mEntryManager.updateRanking(rankingMap, "onNotificationPosted");
-                    }
-                    return;
-                }
-                if (isUpdate) {
-                    mEntryManager.updateNotification(sbn, rankingMap);
-                } else {
-                    mEntryManager.addNotification(sbn, rankingMap);
+                for (NotifServiceListener listener : mNotificationListeners) {
+                    listener.onNotificationPosted(sbn, rankingMap);
                 }
             });
         }
@@ -158,12 +129,10 @@
             int reason) {
         if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason);
         if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
-            final String key = sbn.getKey();
             mMainHandler.post(() -> {
-                if (mDownstreamListener != null) {
-                    mDownstreamListener.onNotificationRemoved(sbn, rankingMap, reason);
+                for (NotifServiceListener listener : mNotificationListeners) {
+                    listener.onNotificationRemoved(sbn, rankingMap, reason);
                 }
-                mEntryManager.removeNotification(key, rankingMap, reason);
             });
         }
     }
@@ -179,10 +148,9 @@
         if (rankingMap != null) {
             RankingMap r = onPluginRankingUpdate(rankingMap);
             mMainHandler.post(() -> {
-                if (mDownstreamListener != null) {
-                    mDownstreamListener.onNotificationRankingUpdate(rankingMap);
+                for (NotifServiceListener listener : mNotificationListeners) {
+                    listener.onNotificationRankingUpdate(r);
                 }
-                mEntryManager.updateNotificationRanking(r);
             });
         }
     }
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 dbefc7b..43b9fbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -33,6 +33,8 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
+import com.android.systemui.statusbar.NotificationListener;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
@@ -174,6 +176,11 @@
         mKeyguardEnvironment = keyguardEnvironment;
     }
 
+    /** Once called, the NEM will start processing notification events from system server. */
+    public void attach(NotificationListener notificationListener) {
+        notificationListener.addNotificationListener(mNotifListener);
+    }
+
     /** Adds a {@link NotificationEntryListener}. */
     public void addNotificationEntryListener(NotificationEntryListener listener) {
         mNotificationEntryListeners.add(listener);
@@ -318,6 +325,36 @@
         }
     }
 
+    private final NotifServiceListener mNotifListener = new NotifServiceListener() {
+        @Override
+        public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+            final boolean isUpdate = mActiveNotifications.containsKey(sbn.getKey());
+            if (isUpdate) {
+                updateNotification(sbn, rankingMap);
+            } else {
+                addNotification(sbn, rankingMap);
+            }
+        }
+
+        @Override
+        public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+            removeNotification(sbn.getKey(), rankingMap, UNDEFINED_DISMISS_REASON);
+        }
+
+        @Override
+        public void onNotificationRemoved(
+                StatusBarNotification sbn,
+                RankingMap rankingMap,
+                int reason) {
+            removeNotification(sbn.getKey(), rankingMap, reason);
+        }
+
+        @Override
+        public void onNotificationRankingUpdate(RankingMap rankingMap) {
+            updateNotificationRanking(rankingMap);
+        }
+    };
+
     /**
      * Equivalent to the old NotificationData#add
      * @param entry - an entry which is prepared for display
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 6c61923..3afd623 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -32,7 +32,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -120,11 +119,6 @@
             return true;
         }
 
-        if (!StatusBar.ENABLE_CHILD_NOTIFICATIONS
-                && mGroupManager.isChildInGroupWithSummary(sbn)) {
-            return true;
-        }
-
         if (getFsc().isDisclosureNotification(sbn)
                 && !getFsc().isDisclosureNeededForUser(sbn.getUserId())) {
             // this is a foreground-service disclosure for a user that does not need to show one
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 9ae3882..ec1efa5 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,6 +24,7 @@
 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
@@ -57,15 +58,22 @@
 
     @VisibleForTesting
     public void setSummary(@Nullable NotificationEntry summary) {
-        mSummary = summary;
+        if (!Objects.equals(mSummary, summary)) {
+            mSummary = summary;
+            onGroupingUpdated();
+        }
     }
 
     void clearChildren() {
-        mChildren.clear();
+        if (mChildren.size() != 0) {
+            mChildren.clear();
+            onGroupingUpdated();
+        }
     }
 
     void addChild(NotificationEntry child) {
         mChildren.add(child);
+        onGroupingUpdated();
     }
 
     void sortChildren(Comparator<? super NotificationEntry> c) {
@@ -77,4 +85,5 @@
     }
 
     public static final GroupEntry ROOT_ENTRY = new GroupEntry("<root>");
+
 }
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 6ce7fd9..052473a 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
@@ -19,6 +19,14 @@
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+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
@@ -26,14 +34,23 @@
  */
 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() {
@@ -53,7 +70,11 @@
 
     @VisibleForTesting
     public void setParent(@Nullable GroupEntry parent) {
-        mParent = parent;
+        if (!Objects.equals(mParent, parent)) {
+            invalidateParent();
+            mParent = parent;
+            onGroupingUpdated();
+        }
     }
 
     @Nullable public GroupEntry getPreviousParent() {
@@ -72,4 +93,58 @@
     void setSection(int section) {
         mSection = section;
     }
+
+    /**
+     * Resets the cached values of DerivedMembers.
+     */
+    void invalidateDerivedMembers() {
+        for (int i = 0; i < mDerivedMemberList.size(); i++) {
+            mDerivedMemberList.get(i).invalidate();
+        }
+    }
+
+    /**
+     * Whether this notification is shown to the user as a high priority notification: visible on
+     * the lock screen/status bar and in the top section in the shade.
+     */
+    public boolean isHighPriority() {
+        return mIsHighPriorityProvider.get(this);
+    }
+
+    private void invalidateParent() {
+        // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children
+        if (getParent() != null) {
+            getParent().invalidateDerivedMembers();
+        }
+
+        // TODO: (b/145659174) remove
+        final NotificationEntry notifEntry = getRepresentativeEntry();
+        if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) {
+            NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn());
+            if (summary != null) {
+                summary.invalidateDerivedMembers();
+            }
+        }
+    }
+
+    void onGroupingUpdated() {
+        for (int i = 0; i < mDerivedMemberList.size(); i++) {
+            mDerivedMemberList.get(i).onGroupingUpdated();
+        }
+        invalidateParent();
+    }
+
+    void onSbnUpdated() {
+        for (int i = 0; i < mDerivedMemberList.size(); i++) {
+            mDerivedMemberList.get(i).onSbnUpdated();
+        }
+        invalidateParent();
+    }
+
+    void onRankingUpdated() {
+        for (int i = 0; i < mDerivedMemberList.size(); i++) {
+            mDerivedMemberList.get(i).onRankingUpdated();
+        }
+        invalidateParent();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 6f085c0..7f85c88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -116,7 +116,7 @@
         }
         mAttached = true;
 
-        listenerService.setDownstreamListener(mNotifServiceListener);
+        listenerService.addNotificationListener(mNotifServiceListener);
     }
 
     /**
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 232fb6d..de16ef5 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
@@ -21,7 +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.Notification.EXTRA_MESSAGES;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
@@ -42,7 +41,6 @@
 import android.content.Context;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
-import android.os.Parcelable;
 import android.os.SystemClock;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.SnoozeCriterion;
@@ -92,7 +90,6 @@
     private StatusBarNotification mSbn;
     private Ranking mRanking;
 
-
     /*
      * Bookkeeping members
      */
@@ -120,7 +117,6 @@
     public int targetSdk;
     private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
     public CharSequence remoteInputText;
-    private final List<Person> mAssociatedPeople = new ArrayList<>();
     private Notification.BubbleMetadata mBubbleMetadata;
 
     /**
@@ -157,12 +153,6 @@
      */
     private boolean hasSentReply;
 
-    /**
-     * 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.
-     */
-    private boolean mHighPriority;
-
     private boolean mSensitive = true;
     private Runnable mOnSensitiveChangedListener;
     private boolean mAutoHeadsUp;
@@ -212,9 +202,11 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        mSbn = sbn;
-        mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
-        updatePeopleList();
+        if (!Objects.equals(mSbn, sbn)) {
+            mSbn = sbn;
+            mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
+            onSbnUpdated();
+        }
     }
 
     /**
@@ -239,10 +231,12 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        mRanking = ranking;
+        if (!Objects.equals(mRanking, ranking)) {
+            mRanking = ranking;
+            onRankingUpdated();
+        }
     }
 
-
     /*
      * Convenience getters for SBN and Ranking members
      */
@@ -304,49 +298,10 @@
         return interruption;
     }
 
-    public boolean isHighPriority() {
-        return mHighPriority;
-    }
-
-    public void setIsHighPriority(boolean highPriority) {
-        this.mHighPriority = highPriority;
-    }
-
     public boolean isBubble() {
         return (mSbn.getNotification().flags & FLAG_BUBBLE) != 0;
     }
 
-    private void updatePeopleList() {
-        mAssociatedPeople.clear();
-
-        Bundle extras = mSbn.getNotification().extras;
-        if (extras == null) {
-            return;
-        }
-
-        List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
-
-        if (p != null) {
-            mAssociatedPeople.addAll(p);
-        }
-
-        if (Notification.MessagingStyle.class.equals(
-                mSbn.getNotification().getNotificationStyle())) {
-            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
-            if (!ArrayUtils.isEmpty(messages)) {
-                for (Notification.MessagingStyle.Message message :
-                        Notification.MessagingStyle.Message
-                                .getMessagesFromBundleArray(messages)) {
-                    mAssociatedPeople.add(message.getSenderPerson());
-                }
-            }
-        }
-    }
-
-    boolean hasAssociatedPeople() {
-        return mAssociatedPeople.size() > 0;
-    }
-
     /**
      * Returns the data needed for a bubble for this notification, if it exists.
      */
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 48a4882..7010943 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
@@ -16,14 +16,11 @@
 
 package com.android.systemui.statusbar.notification.collection
 
-import android.app.NotificationManager.IMPORTANCE_DEFAULT
 import android.app.NotificationManager.IMPORTANCE_HIGH
-import android.app.NotificationManager.IMPORTANCE_LOW
 import android.app.NotificationManager.IMPORTANCE_MIN
 import android.service.notification.NotificationListenerService.Ranking
 import android.service.notification.NotificationListenerService.RankingMap
 import android.service.notification.StatusBarNotification
-import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
@@ -105,44 +102,6 @@
         return entry.key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN
     }
 
-    @VisibleForTesting
-    protected fun isHighPriority(entry: NotificationEntry): Boolean {
-        if (entry.importance >= IMPORTANCE_DEFAULT ||
-                hasHighPriorityCharacteristics(entry)) {
-            return true
-        }
-
-        if (groupManager.isSummaryOfGroup(entry.sbn)) {
-            val logicalChildren = groupManager.getLogicalChildren(entry.sbn)
-            for (child in logicalChildren) {
-                if (isHighPriority(child)) {
-                    return true
-                }
-            }
-        }
-
-        return false
-    }
-
-    private fun hasHighPriorityCharacteristics(entry: NotificationEntry): Boolean {
-        val c = entry.channel
-        val n = entry.sbn.notification
-
-        if ((n.isForegroundService && entry.ranking.importance >= IMPORTANCE_LOW) ||
-                n.hasMediaSession() ||
-                entry.isPeopleNotification()) {
-            // Users who have long pressed and demoted to silent should not see the notification
-            // in the top section
-            if (c != null && c.hasUserSetImportance()) {
-                return false
-            }
-
-            return true
-        }
-
-        return false
-    }
-
     fun updateRanking(
         newRankingMap: RankingMap?,
         entries: Collection<NotificationEntry>,
@@ -219,7 +178,10 @@
                         // TODO: notify group manager here?
                         groupManager.onEntryUpdated(entry, oldSbn)
                     }
-                    entry.setIsHighPriority(isHighPriority(entry))
+
+                    // TODO: (b/145659174) remove after moving to new NotifPipeline
+                    // (should be able to remove all groupManager code post-migration)
+                    entry.invalidateDerivedMembers()
                 }
             }
         }
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 4413dc4..232246e 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
@@ -37,6 +37,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
@@ -133,8 +135,8 @@
                 // to be shown on the lockscreen
                 // TODO: grouping hasn't happened yet (b/145134683)
                 if (entry.getParent() != null) {
-                    final NotificationEntry summary = entry.getParent().getRepresentativeEntry();
-                    if (priorityExceedsLockscreenShowingThreshold(summary)) {
+                    final GroupEntry parent = entry.getParent();
+                    if (priorityExceedsLockscreenShowingThreshold(parent)) {
                         return false;
                     }
                 }
@@ -144,7 +146,7 @@
         }
     };
 
-    private boolean priorityExceedsLockscreenShowingThreshold(NotificationEntry entry) {
+    private boolean priorityExceedsLockscreenShowingThreshold(ListEntry entry) {
         if (entry == null) {
             return false;
         }
@@ -154,7 +156,7 @@
             //  correctly updated before reaching this point (b/145134683)
             return entry.isHighPriority();
         } else {
-            return !entry.getRanking().isAmbient();
+            return !entry.getRepresentativeEntry().getRanking().isAmbient();
         }
     }
 
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
new file mode 100644
index 0000000..815e6f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
@@ -0,0 +1,62 @@
+/*
+ * 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/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
new file mode 100644
index 0000000..76e256b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
@@ -0,0 +1,148 @@
+/*
+ * 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/people/PeopleHubModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
index 4f03003..efcef71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
@@ -31,6 +31,11 @@
     abstract fun peopleHubDataSource(impl: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
 
     @Binds
+    abstract fun peopleHubSettingChangeDataSource(
+        impl: PeopleHubSettingChangeDataSourceImpl
+    ): DataSource<Boolean>
+
+    @Binds
     abstract fun peopleHubViewModelFactoryDataSource(
         impl: PeopleHubViewModelFactoryDataSourceImpl
     ): DataSource<PeopleHubViewModelFactory>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 987b52db..784673e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -249,7 +249,7 @@
     }
 }
 
-private fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
+fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
         entry.row
                 ?.childrenWithId(R.id.expanded)
                 ?.mapNotNull { it as? ViewGroup }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
index 5c35408..e9d6a0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubViewController.kt
@@ -16,7 +16,14 @@
 
 package com.android.systemui.statusbar.notification.people
 
+import android.content.Context
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
 import android.view.View
+import com.android.systemui.dagger.qualifiers.MainHandler
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager
 import javax.inject.Inject
@@ -90,29 +97,58 @@
 @Singleton
 class PeopleHubViewModelFactoryDataSourceImpl @Inject constructor(
     private val activityStarter: ActivityStarter,
-    private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
+    private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>,
+    private val settingChangeSource: DataSource<@JvmSuppressWildcards Boolean>
 ) : DataSource<PeopleHubViewModelFactory> {
 
-    override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>) =
-            dataSource.registerListener(PeopleHubModelListenerImpl(activityStarter, listener))
+    override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>): Subscription {
+        var stripEnabled = false
+        var model: PeopleHubModel? = null
+
+        fun updateListener() {
+            // don't invoke listener until we've received our first model
+            model?.let { model ->
+                val factory =
+                        if (stripEnabled) PeopleHubViewModelFactoryImpl(model, activityStarter)
+                        else EmptyViewModelFactory
+                listener.onDataChanged(factory)
+            }
+        }
+
+        val settingSub = settingChangeSource.registerListener(object : DataListener<Boolean> {
+            override fun onDataChanged(data: Boolean) {
+                stripEnabled = data
+                updateListener()
+            }
+        })
+        val dataSub = dataSource.registerListener(object : DataListener<PeopleHubModel> {
+            override fun onDataChanged(data: PeopleHubModel) {
+                model = data
+                updateListener()
+            }
+        })
+        return object : Subscription {
+            override fun unsubscribe() {
+                settingSub.unsubscribe()
+                dataSub.unsubscribe()
+            }
+        }
+    }
 }
 
-private class PeopleHubModelListenerImpl(
-    private val activityStarter: ActivityStarter,
-    private val dataListener: DataListener<PeopleHubViewModelFactory>
-) : DataListener<PeopleHubModel> {
-
-    override fun onDataChanged(data: PeopleHubModel) =
-            dataListener.onDataChanged(PeopleHubViewModelFactoryImpl(data, activityStarter))
+private object EmptyViewModelFactory : PeopleHubViewModelFactory {
+    override fun createWithAssociatedClickView(view: View): PeopleHubViewModel {
+        return PeopleHubViewModel(emptySequence(), false)
+    }
 }
 
 private class PeopleHubViewModelFactoryImpl(
-    private val data: PeopleHubModel,
+    private val model: PeopleHubModel,
     private val activityStarter: ActivityStarter
 ) : PeopleHubViewModelFactory {
 
     override fun createWithAssociatedClickView(view: View): PeopleHubViewModel {
-        val personViewModels = data.people.asSequence().map { personModel ->
+        val personViewModels = model.people.asSequence().map { personModel ->
             val onClick = {
                 activityStarter.startPendingIntentDismissingKeyguard(
                         personModel.clickIntent,
@@ -122,7 +158,42 @@
             }
             PersonViewModel(personModel.name, personModel.avatar, onClick)
         }
-        return PeopleHubViewModel(personViewModels, data.people.isNotEmpty())
+        return PeopleHubViewModel(personViewModels, model.people.isNotEmpty())
+    }
+}
+
+@Singleton
+class PeopleHubSettingChangeDataSourceImpl @Inject constructor(
+    @MainHandler private val handler: Handler,
+    context: Context
+) : DataSource<Boolean> {
+
+    private val settingUri = Settings.Secure.getUriFor(Settings.Secure.PEOPLE_STRIP)
+    private val contentResolver = context.contentResolver
+
+    override fun registerListener(listener: DataListener<Boolean>): Subscription {
+        // Immediately report current value of setting
+        updateListener(listener)
+        val observer = object : ContentObserver(handler) {
+            override fun onChange(selfChange: Boolean, uri: Uri?, userId: Int) {
+                super.onChange(selfChange, uri, userId)
+                updateListener(listener)
+            }
+        }
+        contentResolver.registerContentObserver(settingUri, false, observer, UserHandle.USER_ALL)
+        return object : Subscription {
+            override fun unsubscribe() = contentResolver.unregisterContentObserver(observer)
+        }
+    }
+
+    private fun updateListener(listener: DataListener<Boolean>) {
+        val setting = Settings.Secure.getIntForUser(
+                contentResolver,
+                Settings.Secure.PEOPLE_STRIP,
+                0,
+                UserHandle.USER_CURRENT
+        )
+        listener.onDataChanged(setting != 0)
     }
 }
 
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 9ac5b44..65423e9 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
@@ -813,15 +813,14 @@
      * @param parent the new parent notification
      */
     public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {
-        boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
         if (mExpandAnimationRunning && !isChildInGroup && mNotificationParent != null) {
             mNotificationParent.setChildIsExpanding(false);
             mNotificationParent.setExtraWidthForClipping(0.0f);
             mNotificationParent.setMinimumHeightForClipping(0);
         }
-        mNotificationParent = childInGroup ? parent : null;
-        mPrivateLayout.setIsChildInGroup(childInGroup);
-        mNotificationInflater.setIsChildInGroup(childInGroup);
+        mNotificationParent = isChildInGroup ? parent : null;
+        mPrivateLayout.setIsChildInGroup(isChildInGroup);
+        mNotificationInflater.setIsChildInGroup(isChildInGroup);
         resetBackgroundAlpha();
         updateBackgroundForGroupState();
         updateClickAndFocus();
@@ -2354,8 +2353,8 @@
     }
 
     private void onChildrenCountChanged() {
-        mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS
-                && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
+        mIsSummaryWithChildren = mChildrenContainer != null
+                && mChildrenContainer.getNotificationChildCount() > 0;
         if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
             mChildrenContainer.recreateNotificationHeader(mExpandClickListener
             );
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index 8e9a051e..2761689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -249,10 +249,8 @@
             }
         }
 
-        if (adjustPeopleHubVisibilityAndPosition(lastPersonIndex)) {
-            // make room for peopleHub
-            firstGentleNotifIndex++;
-        }
+        // make room for peopleHub
+        firstGentleNotifIndex += adjustPeopleHubVisibilityAndPosition(lastPersonIndex);
 
         adjustGentleHeaderVisibilityAndPosition(firstGentleNotifIndex);
 
@@ -296,7 +294,7 @@
         }
     }
 
-    private boolean adjustPeopleHubVisibilityAndPosition(int lastPersonIndex) {
+    private int adjustPeopleHubVisibilityAndPosition(int lastPersonIndex) {
         final boolean showPeopleHeader = mPeopleHubVisible
                 && mNumberOfSections > 2
                 && mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
@@ -307,6 +305,7 @@
         if (!showPeopleHeader) {
             if (currentlyVisible) {
                 mParent.removeView(mPeopleHubView);
+                return -1;
             }
         } else {
             mPeopleHubView.unDismiss();
@@ -317,7 +316,7 @@
                     mPeopleHubView.setTransientContainer(null);
                 }
                 mParent.addView(mPeopleHubView, targetIndex);
-                return true;
+                return 1;
             } else if (currentHubIndex != targetIndex) {
                 if (currentHubIndex < targetIndex) {
                     targetIndex--;
@@ -325,7 +324,7 @@
                 mParent.changeViewPosition(mPeopleHubView, targetIndex);
             }
         }
-        return false;
+        return 0;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index f3e9b6b..183adeb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -37,12 +37,19 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.statusbar.NotificationMediaManager;
 
 import libcore.io.IoUtils;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Objects;
 
 import javax.inject.Inject;
@@ -52,16 +59,15 @@
  * Manages the lockscreen wallpaper.
  */
 @Singleton
-public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable {
+public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
+        Dumpable {
 
     private static final String TAG = "LockscreenWallpaper";
 
-    private final NotificationMediaManager mMediaManager =
-            Dependency.get(NotificationMediaManager.class);
-
+    private final NotificationMediaManager mMediaManager;
     private final WallpaperManager mWallpaperManager;
-    private Handler mH;
     private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final Handler mH;
 
     private boolean mCached;
     private Bitmap mCache;
@@ -74,10 +80,16 @@
     @Inject
     public LockscreenWallpaper(WallpaperManager wallpaperManager,
             @Nullable IWallpaperManager iWallpaperManager,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            DumpController dumpController,
+            NotificationMediaManager mediaManager,
+            @MainHandler Handler mainHandler) {
+        dumpController.registerDumpable(getClass().getSimpleName(), this);
         mWallpaperManager = wallpaperManager;
         mCurrentUserId = ActivityManager.getCurrentUser();
         mUpdateMonitor = keyguardUpdateMonitor;
+        mMediaManager = mediaManager;
+        mH = mainHandler;
 
         if (iWallpaperManager != null) {
             // Service is disabled on some devices like Automotive
@@ -89,14 +101,6 @@
         }
     }
 
-    void setHandler(Handler handler) {
-        if (mH != null) {
-            Log.wtfStack(TAG, "Handler has already been set. Trying to double initialize?");
-            return;
-        }
-        mH = handler;
-    }
-
     public Bitmap getBitmap() {
         if (mCached) {
             return mCache;
@@ -227,6 +231,16 @@
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println(getClass().getSimpleName() + ":");
+        IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "  ").increaseIndent();
+        iPw.println("mCached=" + mCached);
+        iPw.println("mCache=" + mCache);
+        iPw.println("mCurrentUserId=" + mCurrentUserId);
+        iPw.println("mSelectedUser=" + mSelectedUser);
+    }
+
     private static class LoaderResult {
         public final boolean success;
         public final Bitmap bitmap;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 1454e25..4c5bbce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -360,7 +360,8 @@
             return false;
         }
 
-        if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
+        if (mState == ScrimState.AOD
+                && (mDozeParameters.getAlwaysOn() || mDockManager.isDocked())) {
             return true;
         }
 
@@ -560,7 +561,7 @@
     }
 
     protected void scheduleUpdate() {
-        if (mUpdatePending) return;
+        if (mUpdatePending || mScrimBehind == null) return;
 
         // Make sure that a frame gets scheduled.
         mScrimBehind.invalidate();
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 a9d7601..f4c7e23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -246,9 +246,6 @@
         StatusBarStateController.StateListener, ActivityLaunchAnimator.Callback {
     public static final boolean MULTIUSER_DEBUG = false;
 
-    public static final boolean ENABLE_CHILD_NOTIFICATIONS
-            = SystemProperties.getBoolean("debug.child_notifs", true);
-
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
     protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
     protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
@@ -813,6 +810,8 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+        mWallpaperSupported =
+                mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
 
         // Connect in to the status bar manager service
         mCommandQueue.addCallback(this);
@@ -826,9 +825,6 @@
 
         createAndAddWindows(result);
 
-        mWallpaperSupported =
-                mContext.getSystemService(WallpaperManager.class).isWallpaperSupported();
-
         if (mWallpaperSupported) {
             // Make sure we always have the most current wallpaper info.
             IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
@@ -1061,7 +1057,6 @@
 
         if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
             mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
-            mLockscreenWallpaper.setHandler(mHandler);
         }
 
         mKeyguardIndicationController =
@@ -1271,6 +1266,7 @@
         if (mFeatureFlags.isNewNotifPipelineEnabled()) {
             mNewNotifPipeline.get().initialize(mNotificationListener);
         }
+        mEntryManager.attach(mNotificationListener);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
new file mode 100644
index 0000000..62ae7b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.animation
+
+import android.os.Looper
+import android.util.ArrayMap
+import android.util.Log
+import android.view.View
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FlingAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
+import java.util.WeakHashMap
+
+/**
+ * Extension function for all objects which will return a PhysicsAnimator instance for that object.
+ */
+val <T : View> T.physicsAnimator: PhysicsAnimator<T> get() { return getInstance(this) }
+
+private const val TAG = "PhysicsAnimator"
+
+typealias EndAction = () -> Unit
+
+/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
+typealias UpdateMap<T> =
+        ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+/**
+ * Map of the animators associated with a given object. This ensures that only one animator
+ * per object exists.
+ */
+internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
+
+/**
+ * Default spring configuration to use for animations where stiffness and/or damping ratio
+ * were not provided.
+ */
+private val defaultSpring = PhysicsAnimator.SpringConfig(
+        SpringForce.STIFFNESS_MEDIUM,
+        SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
+
+/** Default fling configuration to use for animations where friction was not provided. */
+private val defaultFling = PhysicsAnimator.FlingConfig(
+        friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
+
+/** Whether to log helpful debug information about animations. */
+private var verboseLogging = false
+
+/**
+ * Animator that uses physics-based animations to animate properties on views and objects. Physics
+ * animations use real-world physical concepts, such as momentum and mass, to realistically simulate
+ * motion. PhysicsAnimator is heavily inspired by [android.view.ViewPropertyAnimator], and
+ * also uses the builder pattern to configure and start animations.
+ *
+ * The physics animations are backed by [DynamicAnimation].
+ *
+ * @param T The type of the object being animated.
+ */
+class PhysicsAnimator<T> private constructor (val target: T) {
+
+    /** Data class for representing animation frame updates. */
+    data class AnimationUpdate(val value: Float, val velocity: Float)
+
+    /** [DynamicAnimation] instances for the given properties. */
+    private val springAnimations = ArrayMap<FloatPropertyCompat<in T>, SpringAnimation>()
+    private val flingAnimations = ArrayMap<FloatPropertyCompat<in T>, FlingAnimation>()
+
+    /**
+     * Spring and fling configurations for the properties to be animated on the target. We'll
+     * configure and start the DynamicAnimations for these properties according to the provided
+     * configurations.
+     */
+    private val springConfigs = ArrayMap<FloatPropertyCompat<in T>, SpringConfig>()
+    private val flingConfigs = ArrayMap<FloatPropertyCompat<in T>, FlingConfig>()
+
+    /**
+     * Animation listeners for the animation. These will be notified when each property animation
+     * updates or ends.
+     */
+    private val updateListeners = ArrayList<UpdateListener<T>>()
+    private val endListeners = ArrayList<EndListener<T>>()
+
+    /** End actions to run when all animations have completed.  */
+    private val endActions = ArrayList<EndAction>()
+
+    /**
+     * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
+     * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
+     * just one permanent update and end listener to the DynamicAnimations.
+     */
+    internal var internalListeners = ArrayList<InternalListener>()
+
+    /**
+     * Action to run when [start] is called. This can be changed by
+     * [PhysicsAnimatorTestUtils.prepareForTest] to enable animators to run under test and provide
+     * helpful test utilities.
+     */
+    internal var startAction: () -> Unit = ::startInternal
+
+    /**
+     * Springs a property to the given value, using the provided configuration settings.
+     *
+     * Springs are used when you know the exact value to which you want to animate. They can be
+     * configured with a start velocity (typically used when the spring is initiated by a touch
+     * event), but this velocity will be realistically attenuated as forces are applied to move the
+     * property towards the end value.
+     *
+     * If you find yourself repeating the same stiffness and damping ratios many times, consider
+     * storing a single [SpringConfig] instance and passing that in instead of individual values.
+     *
+     * @param property The property to spring to the given value. The property must be an instance
+     * of FloatPropertyCompat&lt;? super T&gt;. For example, if this is a
+     * PhysicsAnimator&lt;FrameLayout&gt;, you can use a FloatPropertyCompat&lt;FrameLayout&gt;, as
+     * well as a FloatPropertyCompat&lt;ViewGroup&gt;, and so on.
+     * @param toPosition The value to spring the given property to.
+     * @param startVelocity The initial velocity to use for the animation.
+     * @param stiffness The stiffness to use for the spring. Higher stiffness values result in
+     * faster animations, while lower stiffness means a slower animation. Reasonable values for
+     * low, medium, and high stiffness can be found as constants in [SpringForce].
+     * @param dampingRatio The damping ratio (bounciness) to use for the spring. Higher values
+     * result in a less 'springy' animation, while lower values allow the animation to bounce
+     * back and forth for a longer time after reaching the final position. Reasonable values for
+     * low, medium, and high damping can be found in [SpringForce].
+     */
+    fun spring(
+        property: FloatPropertyCompat<in T>,
+        toPosition: Float,
+        startVelocity: Float = 0f,
+        stiffness: Float = defaultSpring.stiffness,
+        dampingRatio: Float = defaultSpring.dampingRatio
+    ): PhysicsAnimator<T> {
+        if (verboseLogging) {
+            Log.d(TAG, "Springing ${getReadablePropertyName(property)} to $toPosition.")
+        }
+
+        springConfigs[property] =
+                SpringConfig(stiffness, dampingRatio, startVelocity, toPosition)
+        return this
+    }
+
+    /**
+     * Springs a property to a given value using the provided start velocity and configuration
+     * options.
+     *
+     * @see spring
+     */
+    fun spring(
+        property: FloatPropertyCompat<in T>,
+        toPosition: Float,
+        startVelocity: Float,
+        config: SpringConfig = defaultSpring
+    ): PhysicsAnimator<T> {
+        return spring(
+                property, toPosition, startVelocity, config.stiffness, config.dampingRatio)
+    }
+
+    /**
+     * Springs a property to a given value using the provided configuration options, and a start
+     * velocity of 0f.
+     *
+     * @see spring
+     */
+    fun spring(
+        property: FloatPropertyCompat<in T>,
+        toPosition: Float,
+        config: SpringConfig = defaultSpring
+    ): PhysicsAnimator<T> {
+        return spring(property, toPosition, 0f, config)
+    }
+
+    /**
+     * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+     * the provided configuration settings.
+     *
+     * Flings are used when you have a start velocity, and want the property value to realistically
+     * decrease as friction is applied until the velocity reaches zero. Flings do not have a
+     * deterministic end value. If you are attempting to animate to a specific end value, use
+     * [spring].
+     *
+     * If you find yourself repeating the same friction/min/max values, consider storing a single
+     * [FlingConfig] and passing that in instead.
+     *
+     * @param property The property to fling using the given start velocity.
+     * @param startVelocity The start velocity (in pixels per second) with which to start the fling.
+     * @param friction Friction value applied to slow down the animation over time. Higher values
+     * will more quickly slow the animation. Typical friction values range from 1f to 10f.
+     * @param min The minimum value allowed for the animation. If this value is reached, the
+     * animation will end abruptly.
+     * @param max The maximum value allowed for the animation. If this value is reached, the
+     * animation will end abruptly.
+     */
+    fun fling(
+        property: FloatPropertyCompat<in T>,
+        startVelocity: Float,
+        friction: Float = defaultFling.friction,
+        min: Float = defaultFling.min,
+        max: Float = defaultFling.max
+    ): PhysicsAnimator<T> {
+        if (verboseLogging) {
+            Log.d(TAG, "Flinging ${getReadablePropertyName(property)} " +
+                    "with velocity $startVelocity.")
+        }
+
+        flingConfigs[property] = FlingConfig(friction, min, max, startVelocity)
+        return this
+    }
+
+    /**
+     * Flings a property using the given start velocity, using a [FlingAnimation] configured using
+     * the provided configuration settings.
+     *
+     * @see fling
+     */
+    fun fling(
+        property: FloatPropertyCompat<in T>,
+        startVelocity: Float,
+        config: FlingConfig = defaultFling
+    ): PhysicsAnimator<T> {
+        return fling(property, startVelocity, config.friction, config.min, config.max)
+    }
+
+    /**
+     * Adds a listener that will be called whenever any property on the animated object is updated.
+     * This will be called on every animation frame, with the current value of the animated object
+     * and the new property values.
+     */
+    fun addUpdateListener(listener: UpdateListener<T>): PhysicsAnimator<T> {
+        updateListeners.add(listener)
+        return this
+    }
+
+    /**
+     * Adds a listener that will be called whenever a property's animation ends. This is useful if
+     * you care about a specific property ending, or want to use the end value/end velocity from a
+     * particular property's animation. If you just want to run an action when all property
+     * animations have ended, use [withEndActions].
+     */
+    fun addEndListener(listener: EndListener<T>): PhysicsAnimator<T> {
+        endListeners.add(listener)
+        return this
+    }
+
+    /**
+     * Adds end actions that will be run sequentially when animations for every property involved in
+     * this specific animation have ended (unless they were explicitly canceled). For example, if
+     * you call:
+     *
+     * animator
+     *   .spring(TRANSLATION_X, ...)
+     *   .spring(TRANSLATION_Y, ...)
+     *   .withEndAction(action)
+     *   .start()
+     *
+     * 'action' will be run when both TRANSLATION_X and TRANSLATION_Y end.
+     *
+     * Other properties may still be animating, if those animations were not started in the same
+     * call. For example:
+     *
+     * animator
+     *   .spring(ALPHA, ...)
+     *   .start()
+     *
+     * animator
+     *   .spring(TRANSLATION_X, ...)
+     *   .spring(TRANSLATION_Y, ...)
+     *   .withEndAction(action)
+     *   .start()
+     *
+     * 'action' will still be run as soon as TRANSLATION_X and TRANSLATION_Y end, even if ALPHA is
+     * still animating.
+     *
+     * If you want to run actions as soon as a subset of property animations have ended, you want
+     * access to the animation's end value/velocity, or you want to run these actions even if the
+     * animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
+     * which indicates that all relevant animations have ended.
+     */
+    fun withEndActions(vararg endActions: EndAction): PhysicsAnimator<T> {
+        this.endActions.addAll(endActions)
+        return this
+    }
+
+    /** Starts the animations! */
+    fun start() {
+        startAction()
+    }
+
+    /**
+     * Starts the animations for real! This is typically called immediately by [start] unless this
+     * animator is under test.
+     */
+    internal fun startInternal() {
+        if (!Looper.getMainLooper().isCurrentThread) {
+            Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
+                    "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
+                    "your test setup.")
+        }
+
+        // Add an internal listener that will dispatch animation events to the provided listeners.
+        internalListeners.add(InternalListener(
+                getAnimatedProperties(),
+                ArrayList(updateListeners),
+                ArrayList(endListeners),
+                ArrayList(endActions)))
+
+        for ((property, config) in flingConfigs) {
+            val currentValue = property.getValue(target)
+
+            // If the fling is already out of bounds, don't start it.
+            if (currentValue <= config.min || currentValue >= config.max) {
+                continue
+            }
+
+            val flingAnim = getFlingAnimation(property)
+            config.applyToAnimation(flingAnim)
+            flingAnim.start()
+        }
+
+        for ((property, config) in springConfigs) {
+            val springAnim = getSpringAnimation(property)
+            config.applyToAnimation(springAnim)
+            springAnim.start()
+        }
+
+        clearAnimator()
+    }
+
+    /** Clear the animator's builder variables. */
+    private fun clearAnimator() {
+        springConfigs.clear()
+        flingConfigs.clear()
+
+        updateListeners.clear()
+        endListeners.clear()
+        endActions.clear()
+    }
+
+    /** Retrieves a spring animation for the given property, building one if needed. */
+    private fun getSpringAnimation(property: FloatPropertyCompat<in T>): SpringAnimation {
+        return springAnimations.getOrPut(
+                property,
+                { configureDynamicAnimation(SpringAnimation(target, property), property)
+                        as SpringAnimation })
+    }
+
+    /** Retrieves a fling animation for the given property, building one if needed. */
+    private fun getFlingAnimation(property: FloatPropertyCompat<in T>): FlingAnimation {
+        return flingAnimations.getOrPut(
+                property,
+                { configureDynamicAnimation(FlingAnimation(target, property), property)
+                        as FlingAnimation })
+    }
+
+    /**
+     * Adds update and end listeners to the DynamicAnimation which will dispatch to the internal
+     * listeners.
+     */
+    private fun configureDynamicAnimation(
+        anim: DynamicAnimation<*>,
+        property: FloatPropertyCompat<in T>
+    ): DynamicAnimation<*> {
+        anim.addUpdateListener { _, value, velocity ->
+            for (i in 0 until internalListeners.size) {
+                internalListeners[i].onInternalAnimationUpdate(property, value, velocity)
+            }
+        }
+        anim.addEndListener { _, canceled, value, velocity ->
+            internalListeners.removeAll {
+                it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+        return anim
+    }
+
+    /**
+     * Internal listener class that receives updates from DynamicAnimation listeners, and dispatches
+     * them to the appropriate update/end listeners. This class is also aware of which properties
+     * were being animated when the end listeners were passed in, so that we can provide the
+     * appropriate value for allEnded to [EndListener.onAnimationEnd].
+     */
+    internal inner class InternalListener constructor(
+        private var properties: Set<FloatPropertyCompat<in T>>,
+        private var updateListeners: List<UpdateListener<T>>,
+        private var endListeners: List<EndListener<T>>,
+        private var endActions: List<EndAction>
+    ) {
+
+        /** The number of properties whose animations haven't ended. */
+        private var numPropertiesAnimating = properties.size
+
+        /**
+         * Update values that haven't yet been dispatched because not all property animations have
+         * updated yet.
+         */
+        private val undispatchedUpdates =
+                ArrayMap<FloatPropertyCompat<in T>, AnimationUpdate>()
+
+        /** Called when a DynamicAnimation updates.  */
+        internal fun onInternalAnimationUpdate(
+            property: FloatPropertyCompat<in T>,
+            value: Float,
+            velocity: Float
+        ) {
+
+            // If this property animation isn't relevant to this listener, ignore it.
+            if (!properties.contains(property)) {
+                return
+            }
+
+            undispatchedUpdates[property] = AnimationUpdate(value, velocity)
+            maybeDispatchUpdates()
+        }
+
+        /**
+         * Called when a DynamicAnimation ends.
+         *
+         * @return True if this listener should be removed from the list of internal listeners, so
+         * it no longer receives updates from DynamicAnimations.
+         */
+        internal fun onInternalAnimationEnd(
+            property: FloatPropertyCompat<in T>,
+            canceled: Boolean,
+            finalValue: Float,
+            finalVelocity: Float
+        ): Boolean {
+
+            // If this property animation isn't relevant to this listener, ignore it.
+            if (!properties.contains(property)) {
+                return false
+            }
+
+            // Dispatch updates if we have one for each property.
+            numPropertiesAnimating--
+            maybeDispatchUpdates()
+
+            // If we didn't have an update for each property, dispatch the update for the ending
+            // property. This guarantees that an update isn't sent for this property *after* we call
+            // onAnimationEnd for that property.
+            if (undispatchedUpdates.contains(property)) {
+                updateListeners.forEach { updateListener ->
+                    updateListener.onAnimationUpdateForProperty(
+                            target,
+                            UpdateMap<T>().also { it[property] = undispatchedUpdates[property] })
+                }
+
+                undispatchedUpdates.remove(property)
+            }
+
+            val allEnded = !arePropertiesAnimating(properties)
+            endListeners.forEach {
+                it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+
+            // If all of the animations that this listener cares about have ended, run the end
+            // actions unless the animation was canceled.
+            if (allEnded && !canceled) {
+                endActions.forEach { it() }
+            }
+
+            return allEnded
+        }
+
+        /**
+         * Dispatch undispatched values if we've received an update from each of the animating
+         * properties.
+         */
+        private fun maybeDispatchUpdates() {
+            if (undispatchedUpdates.size >= numPropertiesAnimating &&
+                    undispatchedUpdates.size > 0) {
+                updateListeners.forEach {
+                    it.onAnimationUpdateForProperty(target, ArrayMap(undispatchedUpdates))
+                }
+
+                undispatchedUpdates.clear()
+            }
+        }
+    }
+
+    /** Return true if any animations are running on the object.  */
+    fun isRunning(): Boolean {
+        return arePropertiesAnimating(springAnimations.keys.union(flingAnimations.keys))
+    }
+
+    /** Returns whether the given property is animating.  */
+    fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
+        return springAnimations[property]?.isRunning ?: false
+    }
+
+    /** Returns whether any of the given properties are animating.  */
+    fun arePropertiesAnimating(properties: Set<FloatPropertyCompat<in T>>): Boolean {
+        return properties.any { isPropertyAnimating(it) }
+    }
+
+    /** Return the set of properties that will begin animating upon calling [start]. */
+    internal fun getAnimatedProperties(): Set<FloatPropertyCompat<in T>> {
+        return springConfigs.keys.union(flingConfigs.keys)
+    }
+
+    /** Cancels all in progress animations on all properties. */
+    fun cancel() {
+        for (dynamicAnim in flingAnimations.values.union(springAnimations.values)) {
+            dynamicAnim.cancel()
+        }
+    }
+
+    /**
+     * Container object for spring animation configuration settings. This allows you to store
+     * default stiffness and damping ratio values in a single configuration object, which you can
+     * pass to [spring].
+     */
+    data class SpringConfig internal constructor(
+        internal var stiffness: Float,
+        internal var dampingRatio: Float,
+        internal var startVel: Float = 0f,
+        internal var finalPosition: Float = -Float.MAX_VALUE
+    ) {
+
+        constructor() :
+                this(defaultSpring.stiffness, defaultSpring.dampingRatio)
+
+        constructor(stiffness: Float, dampingRatio: Float) :
+                this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+
+        /** Apply these configuration settings to the given SpringAnimation. */
+        internal fun applyToAnimation(anim: SpringAnimation) {
+            val springForce = anim.spring ?: SpringForce()
+            anim.spring = springForce.apply {
+                stiffness = this@SpringConfig.stiffness
+                dampingRatio = this@SpringConfig.dampingRatio
+                finalPosition = this@SpringConfig.finalPosition
+            }
+
+            if (startVel != 0f) anim.setStartVelocity(startVel)
+        }
+    }
+
+    /**
+     * Container object for fling animation configuration settings. This allows you to store default
+     * friction values (as well as optional min/max values) in a single configuration object, which
+     * you can pass to [fling] and related methods.
+     */
+    data class FlingConfig internal constructor(
+        internal var friction: Float,
+        internal var min: Float,
+        internal var max: Float,
+        internal var startVel: Float
+    ) {
+
+        constructor() : this(defaultFling.friction)
+
+        constructor(friction: Float) :
+                this(friction, defaultFling.min, defaultFling.max)
+
+        constructor(friction: Float, min: Float, max: Float) :
+                this(friction, min, max, startVel = 0f)
+
+        /** Apply these configuration settings to the given FlingAnimation. */
+        internal fun applyToAnimation(anim: FlingAnimation) {
+            anim.apply {
+                friction = this@FlingConfig.friction
+                setMinValue(min)
+                setMaxValue(max)
+                setStartVelocity(startVel)
+            }
+        }
+    }
+
+    /**
+     * Listener for receiving values from in progress animations. Used with
+     * [PhysicsAnimator.addUpdateListener].
+     *
+     * @param <T> The type of the object being animated.
+    </T> */
+    interface UpdateListener<T> {
+
+        /**
+         * Called on each animation frame with the target object, and a map of FloatPropertyCompat
+         * -> AnimationUpdate, containing the latest value and velocity for that property. When
+         * multiple properties are animating together, the map will typically contain one entry for
+         * each property. However, you should never assume that this is the case - when a property
+         * animation ends earlier than the others, you'll receive an UpdateMap containing only that
+         * property's final update. Subsequently, you'll only receive updates for the properties
+         * that are still animating.
+         *
+         * Always check that the map contains an update for the property you're interested in before
+         * accessing it.
+         *
+         * @param target The animated object itself.
+         * @param values Map of property to AnimationUpdate, which contains that property
+         * animation's latest value and velocity. You should never assume that a particular property
+         * is present in this map.
+         */
+        fun onAnimationUpdateForProperty(
+            target: T,
+            values: UpdateMap<T>
+        )
+    }
+
+    /**
+     * Listener for receiving callbacks when animations end.
+     *
+     * @param <T> The type of the object being animated.
+    </T> */
+    interface EndListener<T> {
+
+        /**
+         * Called with the final animation values as each property animation ends. This can be used
+         * to respond to specific property animations concluding (such as hiding a view when ALPHA
+         * ends, even if the corresponding TRANSLATION animations have not ended).
+         *
+         * If you just want to run an action when all of the property animations have ended, you can
+         * use [PhysicsAnimator.withEndActions].
+         *
+         * @param target The animated object itself.
+         * @param property The property whose animation has just ended.
+         * @param canceled Whether the animation was explicitly canceled before it naturally ended.
+         * @param finalValue The final value of the animated property.
+         * @param finalVelocity The final velocity (in pixels per second) of the ended animation.
+         * This is typically zero, unless this was a fling animation which ended abruptly due to
+         * reaching its configured min/max values.
+         * @param allRelevantPropertyAnimsEnded Whether all properties relevant to this end listener
+         * have ended. Relevant properties are those which were animated alongside the
+         * [addEndListener] call where this animator was passed in. For example:
+         *
+         * animator
+         *    .spring(TRANSLATION_X, 100f)
+         *    .spring(TRANSLATION_Y, 200f)
+         *    .withEndListener(firstEndListener)
+         *    .start()
+         *
+         * firstEndListener will be called first for TRANSLATION_X, with allEnded = false,
+         * because TRANSLATION_Y is still running. When TRANSLATION_Y ends, it'll be called with
+         * allEnded = true.
+         *
+         * If a subsequent call to start() is made with other properties, those properties are not
+         * considered relevant and allEnded will still equal true when only TRANSLATION_X and
+         * TRANSLATION_Y end. For example, if immediately after the prior example, while
+         * TRANSLATION_X and TRANSLATION_Y are still animating, we called:
+         *
+         * animator.
+         *    .spring(SCALE_X, 2f, stiffness = 10f) // That will take awhile...
+         *    .withEndListener(secondEndListener)
+         *    .start()
+         *
+         * firstEndListener will still be called with allEnded = true when TRANSLATION_X/Y end, even
+         * though SCALE_X is still animating. Similarly, secondEndListener will be called with
+         * allEnded = true as soon as SCALE_X ends, even if the translation animations are still
+         * running.
+         */
+        fun onAnimationEnd(
+            target: T,
+            property: FloatPropertyCompat<in T>,
+            canceled: Boolean,
+            finalValue: Float,
+            finalVelocity: Float,
+            allRelevantPropertyAnimsEnded: Boolean
+        )
+    }
+
+    companion object {
+
+        /**
+         * Constructor to use to for new physics animator instances in [getInstance]. This is
+         * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
+         * all code using the physics animator is given testable instances instead.
+         */
+        internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
+
+        @JvmStatic
+        fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
+            if (!animators.containsKey(target)) {
+                animators[target] = instanceConstructor(target)
+            }
+
+            return animators[target] as PhysicsAnimator<T>
+        }
+
+        /**
+         * Set whether all physics animators should log a lot of information about animations.
+         * Useful for debugging!
+         */
+        @JvmStatic
+        fun setVerboseLogging(debug: Boolean) {
+            verboseLogging = debug
+        }
+
+        @JvmStatic
+        fun getReadablePropertyName(property: FloatPropertyCompat<*>): String {
+            return when (property) {
+                DynamicAnimation.TRANSLATION_X -> "translationX"
+                DynamicAnimation.TRANSLATION_Y -> "translationY"
+                DynamicAnimation.TRANSLATION_Z -> "translationZ"
+                DynamicAnimation.SCALE_X -> "scaleX"
+                DynamicAnimation.SCALE_Y -> "scaleY"
+                DynamicAnimation.ROTATION -> "rotation"
+                DynamicAnimation.ROTATION_X -> "rotationX"
+                DynamicAnimation.ROTATION_Y -> "rotationY"
+                DynamicAnimation.SCROLL_X -> "scrollX"
+                DynamicAnimation.SCROLL_Y -> "scrollY"
+                DynamicAnimation.ALPHA -> "alpha"
+                else -> "Custom FloatPropertyCompat instance"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
new file mode 100644
index 0000000..a1f74eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util.animation
+
+import android.os.Handler
+import android.os.Looper
+import android.util.ArrayMap
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import java.util.ArrayDeque
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+typealias UpdateMatcher = (PhysicsAnimator.AnimationUpdate) -> Boolean
+typealias UpdateFramesPerProperty<T> =
+        ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>
+
+/**
+ * Utilities for testing code that uses [PhysicsAnimator].
+ *
+ * Start by calling [prepareForTest] at the beginning of each test - this will modify the behavior
+ * of all PhysicsAnimator instances so that they post animations to the main thread (so they don't
+ * crash). It'll also enable the use of the other static helper methods in this class, which you can
+ * use to do things like block the test until animations complete (so you can test end states), or
+ * verify keyframes.
+ */
+object PhysicsAnimatorTestUtils {
+    var timeoutMs: Long = 2000
+    private var startBlocksUntilAnimationsEnd = false
+    private val animationThreadHandler = Handler(Looper.getMainLooper())
+    private val allAnimatedObjects = HashSet<Any>()
+    private val animatorTestHelpers = HashMap<PhysicsAnimator<*>, AnimatorTestHelper<*>>()
+
+    /**
+     * Modifies the behavior of all [PhysicsAnimator] instances so that they post animations to the
+     * main thread, and report all of their
+     */
+    @JvmStatic
+    fun prepareForTest() {
+        val defaultConstructor = PhysicsAnimator.instanceConstructor
+        PhysicsAnimator.instanceConstructor = fun(target: Any): PhysicsAnimator<*> {
+            val animator = defaultConstructor(target)
+            allAnimatedObjects.add(target)
+            animatorTestHelpers[animator] = AnimatorTestHelper(animator)
+            return animator
+        }
+
+        timeoutMs = 2000
+        startBlocksUntilAnimationsEnd = false
+        allAnimatedObjects.clear()
+    }
+
+    @JvmStatic
+    fun tearDown() {
+        val latch = CountDownLatch(1)
+        animationThreadHandler.post {
+            animatorTestHelpers.keys.forEach { it.cancel() }
+            latch.countDown()
+        }
+
+        latch.await()
+
+        animatorTestHelpers.clear()
+        animators.clear()
+        allAnimatedObjects.clear()
+    }
+
+    /**
+     * Sets the maximum time (in milliseconds) to block the test thread while waiting for animations
+     * before throwing an exception.
+     */
+    @JvmStatic
+    fun setBlockTimeout(timeoutMs: Long) {
+        this.timeoutMs = timeoutMs
+    }
+
+    /**
+     * Sets whether all animations should block the test thread until they end. This is typically
+     * the desired behavior, since you can invoke code that runs an animation and then assert things
+     * about its end state.
+     */
+    @JvmStatic
+    fun setAllAnimationsBlock(block: Boolean) {
+        startBlocksUntilAnimationsEnd = block
+    }
+
+    /**
+     * Blocks the calling thread until animations of the given property on the target object end.
+     */
+    @JvmStatic
+    @Throws(InterruptedException::class)
+    fun <T : Any> blockUntilAnimationsEnd(
+        animator: PhysicsAnimator<T>,
+        vararg properties: FloatPropertyCompat<in T>
+    ) {
+        val animatingProperties = HashSet<FloatPropertyCompat<in T>>()
+        for (property in properties) {
+            if (animator.isPropertyAnimating(property)) {
+                animatingProperties.add(property)
+            }
+        }
+
+        if (animatingProperties.size > 0) {
+            val latch = CountDownLatch(animatingProperties.size)
+            getAnimationTestHelper(animator).addTestEndListener(
+                    object : PhysicsAnimator.EndListener<T> {
+                override fun onAnimationEnd(
+                    target: T,
+                    property: FloatPropertyCompat<in T>,
+                    canceled: Boolean,
+                    finalValue: Float,
+                    finalVelocity: Float,
+                    allRelevantPropertyAnimsEnded: Boolean
+                ) {
+                    if (animatingProperties.contains(property)) {
+                        latch.countDown()
+                    }
+                }
+            })
+
+            latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+        }
+    }
+
+    /**
+     * Blocks the calling thread until all animations of the given property (on all target objects)
+     * have ended. Useful when you don't have access to the objects being animated, but still need
+     * to wait for them to end so that other testable side effects occur (such as update/end
+     * listeners).
+     */
+    @JvmStatic
+    @Throws(InterruptedException::class)
+    fun <T : Any> blockUntilAnimationsEnd(
+        properties: FloatPropertyCompat<in T>
+    ) {
+        for (target in allAnimatedObjects) {
+            try {
+                blockUntilAnimationsEnd(
+                        PhysicsAnimator.getInstance(target) as PhysicsAnimator<T>, properties)
+            } catch (e: ClassCastException) {
+                // Keep checking the other objects for ones whose types match the provided
+                // properties.
+            }
+        }
+    }
+
+    /**
+     * Blocks the calling thread until the first animation frame in which predicate returns true. If
+     * the given object isn't animating, returns without blocking.
+     */
+    @JvmStatic
+    @Throws(InterruptedException::class)
+    fun <T : Any> blockUntilFirstAnimationFrameWhereTrue(
+        animator: PhysicsAnimator<T>,
+        predicate: (T) -> Boolean
+    ) {
+        if (animator.isRunning()) {
+            val latch = CountDownLatch(1)
+            getAnimationTestHelper(animator).addTestUpdateListener(object : PhysicsAnimator
+            .UpdateListener<T> {
+                override fun onAnimationUpdateForProperty(
+                    target: T,
+                    values: UpdateMap<T>
+                ) {
+                    if (predicate(target)) {
+                        latch.countDown()
+                    }
+                }
+            })
+
+            latch.await(timeoutMs, TimeUnit.MILLISECONDS)
+        }
+    }
+
+    /**
+     * Verifies that the animator reported animation frame values to update listeners that satisfy
+     * the given matchers, in order. Not all frames need to satisfy a matcher - we'll run through
+     * all animation frames, and check them against the current predicate. If it returns false, we
+     * continue through the frames until it returns true, and then move on to the next matcher.
+     * Verification fails if we run out of frames while unsatisfied matchers remain.
+     *
+     * If verification is successful, all frames to this point are considered 'verified' and will be
+     * cleared. Subsequent calls to this method will start verification at the next animation frame.
+     *
+     * Example: Verify that an animation surpassed x = 50f before going negative.
+     * verifyAnimationUpdateFrames(
+     *    animator, TRANSLATION_X,
+     *    { u -> u.value > 50f },
+     *    { u -> u.value < 0f })
+     *
+     * Example: verify that an animation went backwards at some point while still being on-screen.
+     * verifyAnimationUpdateFrames(
+     *    animator, TRANSLATION_X,
+     *    { u -> u.velocity < 0f && u.value >= 0f })
+     *
+     * This method is intended to help you test longer, more complicated animations where it's
+     * critical that certain values were reached. Using this method to test short animations can
+     * fail due to the animation having fewer frames than provided matchers. For example, an
+     * animation from x = 1f to x = 5f might only have two frames, at x = 3f and x = 5f. The
+     * following would then fail despite it seeming logically sound:
+     *
+     * verifyAnimationUpdateFrames(
+     *    animator, TRANSLATION_X,
+     *    { u -> u.value > 1f },
+     *    { u -> u.value > 2f },
+     *    { u -> u.value > 3f })
+     *
+     * Tests might also fail if your matchers are too granular, such as this example test after an
+     * animation from x = 0f to x = 100f. It's unlikely there was a frame specifically between 2f
+     * and 3f.
+     *
+     * verifyAnimationUpdateFrames(
+     *    animator, TRANSLATION_X,
+     *    { u -> u.value > 2f && u.value < 3f },
+     *    { u -> u.value >= 50f })
+     *
+     * Failures will print a helpful log of all animation frames so you can see what caused the test
+     * to fail.
+     */
+    fun <T : Any> verifyAnimationUpdateFrames(
+        animator: PhysicsAnimator<T>,
+        property: FloatPropertyCompat<in T>,
+        firstUpdateMatcher: UpdateMatcher,
+        vararg additionalUpdateMatchers: UpdateMatcher
+    ) {
+        val updateFrames: UpdateFramesPerProperty<T> = getAnimationUpdateFrames(animator)
+        val matchers = ArrayDeque<UpdateMatcher>(
+                additionalUpdateMatchers.toList())
+        val frameTraceMessage = StringBuilder()
+
+        var curMatcher = firstUpdateMatcher
+
+        // Loop through the updates from the testable animator.
+        for (update in updateFrames[property]
+                ?: error("No frames for given target object and property.")) {
+
+            // Check whether this frame satisfies the current matcher.
+            if (curMatcher(update)) {
+
+                // If that was the last unsatisfied matcher, we're good here. 'Verify' all remaining
+                // frames and return without failing.
+                if (matchers.size == 0) {
+                    getAnimationUpdateFrames(animator).remove(property)
+                    return
+                }
+
+                frameTraceMessage.append("$update\t(satisfied matcher)\n")
+                curMatcher = matchers.pop() // Get the next matcher and keep going.
+            } else {
+                frameTraceMessage.append("${update}\n")
+            }
+        }
+
+        val readablePropertyName = PhysicsAnimator.getReadablePropertyName(property)
+        getAnimationUpdateFrames(animator).remove(property)
+
+        throw RuntimeException(
+                "Failed to verify animation frames for property $readablePropertyName: " +
+                        "Provided ${additionalUpdateMatchers.size + 1} matchers, " +
+                        "however ${matchers.size + 1} remained unsatisfied.\n\n" +
+                        "All frames:\n$frameTraceMessage")
+    }
+
+    /**
+     * Overload of [verifyAnimationUpdateFrames] that builds matchers for you, from given float
+     * values. For example, to verify that an animations passed from 0f to 50f to 100f back to 50f:
+     *
+     * verifyAnimationUpdateFrames(animator, TRANSLATION_X, 0f, 50f, 100f, 50f)
+     *
+     * This verifies that update frames were received with values of >= 0f, >= 50f, >= 100f, and
+     * <= 50f.
+     *
+     * The same caveats apply: short animations might not have enough frames to satisfy all of the
+     * matchers, and overly specific calls (such as 0f, 1f, 2f, 3f, etc. for an animation from
+     * x = 0f to x = 100f) might fail as the animation only had frames at 0f, 25f, 50f, 75f, and
+     * 100f. As with [verifyAnimationUpdateFrames], failures will print a helpful log of all frames
+     * so you can see what caused the test to fail.
+     */
+    fun <T : Any> verifyAnimationUpdateFrames(
+        animator: PhysicsAnimator<T>,
+        property: FloatPropertyCompat<in T>,
+        startValue: Float,
+        firstTargetValue: Float,
+        vararg additionalTargetValues: Float
+    ) {
+        val matchers = ArrayList<UpdateMatcher>()
+
+        val values = ArrayList<Float>().also {
+            it.add(firstTargetValue)
+            it.addAll(additionalTargetValues.toList())
+        }
+
+        var prevVal = startValue
+        for (value in values) {
+            if (value > prevVal) {
+                matchers.add { update -> update.value >= value }
+            } else {
+                matchers.add { update -> update.value <= value }
+            }
+
+            prevVal = value
+        }
+
+        verifyAnimationUpdateFrames(
+                animator, property, matchers[0], *matchers.drop(0).toTypedArray())
+    }
+
+    /**
+     * Returns all of the values that have ever been reported to update listeners, per property.
+     */
+    fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
+            UpdateFramesPerProperty<T> {
+        return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
+    }
+
+    /**
+     * Clears animation frame updates from the given animator so they aren't used the next time its
+     * passed to [verifyAnimationUpdateFrames].
+     */
+    fun <T : Any> clearAnimationUpdateFrames(animator: PhysicsAnimator<T>) {
+        animatorTestHelpers[animator]?.clearUpdates()
+    }
+
+    private fun <T> getAnimationTestHelper(animator: PhysicsAnimator<T>): AnimatorTestHelper<T> {
+        return animatorTestHelpers[animator] as AnimatorTestHelper<T>
+    }
+
+    /**
+     * Helper class for testing an animator. This replaces the animator's start action with
+     * [startForTest] and adds test listeners to enable other test utility behaviors. We build one
+     * these for each Animator and keep them around so we can access the updates.
+     */
+    class AnimatorTestHelper<T> (private val animator: PhysicsAnimator<T>) {
+
+        /** All updates received for each property animation. */
+        private val allUpdates =
+                ArrayMap<FloatPropertyCompat<in T>, ArrayList<PhysicsAnimator.AnimationUpdate>>()
+
+        private val testEndListeners = ArrayList<PhysicsAnimator.EndListener<T>>()
+        private val testUpdateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>()
+
+        init {
+            animator.startAction = ::startForTest
+        }
+
+        internal fun addTestEndListener(listener: PhysicsAnimator.EndListener<T>) {
+            testEndListeners.add(listener)
+        }
+
+        internal fun addTestUpdateListener(listener: PhysicsAnimator.UpdateListener<T>) {
+            testUpdateListeners.add(listener)
+        }
+
+        internal fun getUpdates(): UpdateFramesPerProperty<T> {
+            return allUpdates
+        }
+
+        internal fun clearUpdates() {
+            allUpdates.clear()
+        }
+
+        private fun startForTest() {
+            // The testable animator needs to block the main thread until super.start() has been
+            // called, since callers expect .start() to be synchronous but we're posting it to a
+            // handler here. We may also continue blocking until all animations end, if
+            // startBlocksUntilAnimationsEnd = true.
+            val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
+
+            animationThreadHandler.post {
+                val animatedProperties = animator.getAnimatedProperties()
+
+                // Add an update listener that dispatches to any test update listeners added by
+                // tests.
+                animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
+                    override fun onAnimationUpdateForProperty(
+                        target: T,
+                        values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+                    ) {
+                        for (listener in testUpdateListeners) {
+                            listener.onAnimationUpdateForProperty(target, values)
+                        }
+                    }
+                })
+
+                // Add an end listener that dispatches to any test end listeners added by tests, and
+                // unblocks the main thread if required.
+                animator.addEndListener(object : PhysicsAnimator.EndListener<T> {
+                    override fun onAnimationEnd(
+                        target: T,
+                        property: FloatPropertyCompat<in T>,
+                        canceled: Boolean,
+                        finalValue: Float,
+                        finalVelocity: Float,
+                        allRelevantPropertyAnimsEnded: Boolean
+                    ) {
+                        for (listener in testEndListeners) {
+                            listener.onAnimationEnd(
+                                    target, property, canceled, finalValue, finalVelocity,
+                                    allRelevantPropertyAnimsEnded)
+                        }
+
+                        if (allRelevantPropertyAnimsEnded) {
+                            testEndListeners.clear()
+                            testUpdateListeners.clear()
+
+                            if (startBlocksUntilAnimationsEnd) {
+                                unblockLatch.countDown()
+                            }
+                        }
+                    }
+                })
+
+                val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
+                    it.add(object : PhysicsAnimator.UpdateListener<T> {
+                        override fun onAnimationUpdateForProperty(
+                            target: T,
+                            values: ArrayMap<FloatPropertyCompat<in T>,
+                                             PhysicsAnimator.AnimationUpdate>
+                        ) {
+                            values.forEach { (property, value) ->
+                                allUpdates.getOrPut(property, { ArrayList() }).add(value)
+                            }
+                        }
+                    })
+                }
+
+                /**
+                 * Add an internal listener at the head of the list that captures update values
+                 * directly from DynamicAnimation. We use this to build a list of all updates so we
+                 * can verify that InternalListener dispatches to the real listeners properly.
+                 */
+                animator.internalListeners.add(0, animator.InternalListener(
+                        animatedProperties,
+                        updateListeners,
+                        ArrayList(),
+                        ArrayList()))
+
+                animator.startInternal()
+                unblockLatch.countDown()
+            }
+
+            unblockLatch.await(timeoutMs, TimeUnit.MILLISECONDS)
+        }
+    }
+}
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 24f49ff..3e90581 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -18,11 +18,12 @@
 
 import android.content.Context;
 import android.os.Handler;
+import android.os.Looper;
 
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.BgLooper;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.MainLooper;
 
 import java.util.concurrent.Executor;
 
@@ -38,8 +39,8 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    public static Executor provideExecutor(@BgHandler Handler handler) {
-        return new ExecutorImpl(handler);
+    public static Executor provideExecutor(@BgLooper Looper looper) {
+        return new ExecutorImpl(new Handler(looper));
     }
 
     /**
@@ -47,8 +48,8 @@
      */
     @Provides
     @Background
-    public static Executor provideBackgroundExecutor(@BgHandler Handler handler) {
-        return new ExecutorImpl(handler);
+    public static Executor provideBackgroundExecutor(@BgLooper Looper looper) {
+        return new ExecutorImpl(new Handler(looper));
     }
 
     /**
@@ -64,8 +65,8 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
-    public static DelayableExecutor provideDelayableExecutor(@BgHandler Handler handler) {
-        return new ExecutorImpl(handler);
+    public static DelayableExecutor provideDelayableExecutor(@BgLooper Looper looper) {
+        return new ExecutorImpl(new Handler(looper));
     }
 
     /**
@@ -73,8 +74,8 @@
      */
     @Provides
     @Background
-    public static DelayableExecutor provideBackgroundDelayableExecutor(@BgHandler Handler handler) {
-        return new ExecutorImpl(handler);
+    public static DelayableExecutor provideBackgroundDelayableExecutor(@BgLooper Looper looper) {
+        return new ExecutorImpl(new Handler(looper));
     }
 
     /**
@@ -82,7 +83,7 @@
      */
     @Provides
     @Main
-    public static DelayableExecutor provideMainDelayableExecutor(@MainHandler Handler handler) {
-        return new ExecutorImpl(handler);
+    public static DelayableExecutor provideMainDelayableExecutor(@MainLooper Looper looper) {
+        return new ExecutorImpl(new Handler(looper));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index df67637..25cc9a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.biometrics;
 
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -24,7 +26,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
-import android.hardware.biometrics.Authenticator;
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.Bundle;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -292,9 +293,9 @@
     private Bundle buildBiometricPromptBundle(boolean allowDeviceCredential) {
         Bundle bundle = new Bundle();
         bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
-        int authenticators = Authenticator.TYPE_BIOMETRIC;
+        int authenticators = Authenticators.BIOMETRIC_WEAK;
         if (allowDeviceCredential) {
-            authenticators |= Authenticator.TYPE_CREDENTIAL;
+            authenticators |= Authenticators.DEVICE_CREDENTIAL;
         } else {
             bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative");
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index 6e438e8..162b16e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.biometrics;
 
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -26,7 +28,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
-import android.hardware.biometrics.Authenticator;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.Bundle;
@@ -64,7 +65,7 @@
 
     @Test
     public void testActionAuthenticated_sendsDismissedAuthenticated() {
-        initializeContainer(Authenticator.TYPE_BIOMETRIC);
+        initializeContainer(Authenticators.BIOMETRIC_WEAK);
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_AUTHENTICATED);
@@ -73,7 +74,7 @@
 
     @Test
     public void testActionUserCanceled_sendsDismissedUserCanceled() {
-        initializeContainer(Authenticator.TYPE_BIOMETRIC);
+        initializeContainer(Authenticators.BIOMETRIC_WEAK);
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_USER_CANCELED);
@@ -82,7 +83,7 @@
 
     @Test
     public void testActionButtonNegative_sendsDismissedButtonNegative() {
-        initializeContainer(Authenticator.TYPE_BIOMETRIC);
+        initializeContainer(Authenticators.BIOMETRIC_WEAK);
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
@@ -91,7 +92,7 @@
 
     @Test
     public void testActionTryAgain_sendsTryAgain() {
-        initializeContainer(Authenticator.TYPE_BIOMETRIC);
+        initializeContainer(Authenticators.BIOMETRIC_WEAK);
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
@@ -100,7 +101,7 @@
 
     @Test
     public void testActionError_sendsDismissedError() {
-        initializeContainer(Authenticator.TYPE_BIOMETRIC);
+        initializeContainer(Authenticators.BIOMETRIC_WEAK);
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_ERROR);
@@ -110,7 +111,7 @@
     @Test
     public void testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
         initializeContainer(
-                Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL);
+                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
 
         mAuthContainer.mBiometricCallback.onAction(
                 AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
@@ -125,7 +126,7 @@
     @Test
     public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
         initializeContainer(
-                Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL);
+                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
 
         mAuthContainer.mBiometricView = mock(AuthBiometricView.class);
         mAuthContainer.animateToCredentialUI();
@@ -134,7 +135,7 @@
 
     @Test
     public void testShowBiometricUI() {
-        initializeContainer(Authenticator.TYPE_BIOMETRIC);
+        initializeContainer(Authenticators.BIOMETRIC_WEAK);
 
         assertNotEquals(null, mAuthContainer.mBiometricView);
 
@@ -146,7 +147,7 @@
 
     @Test
     public void testShowCredentialUI_doesNotInflateBiometricUI() {
-        initializeContainer(Authenticator.TYPE_CREDENTIAL);
+        initializeContainer(Authenticators.DEVICE_CREDENTIAL);
 
         mAuthContainer.onAttachedToWindowInternal();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index f6375fc..c0e92e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.biometrics;
 
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertNotNull;
@@ -38,7 +40,6 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.hardware.biometrics.Authenticator;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
@@ -109,28 +110,28 @@
 
     @Test
     public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
     }
 
     @Test
     public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
     }
 
     @Test
     public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
     }
 
     @Test
     public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
         verify(mReceiver).onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED);
@@ -138,14 +139,14 @@
 
     @Test
     public void testSendsReasonError_whenDismissedByError() throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
     }
 
     @Test
     public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
     }
@@ -153,7 +154,7 @@
     @Test
     public void testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated()
             throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED);
     }
@@ -163,20 +164,20 @@
     @Test
     public void testShowInvoked_whenSystemRequested()
             throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         verify(mDialog1).show(any(), any());
     }
 
     @Test
     public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onBiometricAuthenticated();
         verify(mDialog1).onAuthenticationSucceeded();
     }
 
     @Test
     public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onBiometricError(BiometricAuthenticator.TYPE_NONE,
                 BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
                 0 /* vendorCode */);
@@ -189,7 +190,7 @@
 
     @Test
     public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
         final int vendorCode = 0;
         mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
@@ -202,7 +203,7 @@
 
     @Test
     public void testOnHelpInvoked_whenSystemRequested() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         final String helpMessage = "help";
         mAuthController.onBiometricHelp(helpMessage);
 
@@ -214,7 +215,7 @@
 
     @Test
     public void testOnErrorInvoked_whenSystemRequested() throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         final int error = 1;
         final int vendorCode = 0;
         mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
@@ -227,7 +228,7 @@
 
     @Test
     public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
         final int vendorCode = 0;
 
@@ -240,7 +241,7 @@
 
     @Test
     public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
         final int vendorCode = 0;
 
@@ -253,7 +254,7 @@
 
     @Test
     public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
         final int vendorCode = 0;
 
@@ -266,7 +267,7 @@
 
     @Test
     public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
         final int vendorCode = 0;
 
@@ -278,30 +279,24 @@
     }
 
     @Test
-    public void testDismissWithoutCallbackInvoked_whenSystemRequested() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
-        mAuthController.hideAuthenticationDialog();
-        verify(mDialog1).dismissFromSystemServer();
-    }
-
-    @Test
-    public void testClientNotified_whenDismissedBySystemServer() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+    public void testHideAuthenticationDialog_invokesDismissFromSystemServer() {
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.hideAuthenticationDialog();
         verify(mDialog1).dismissFromSystemServer();
 
-        assertNotNull(mAuthController.mCurrentDialog);
-        assertNotNull(mAuthController.mReceiver);
+        // In this case, BiometricService sends the error to the client immediately, without
+        // doing a round trip to SystemUI.
+        assertNull(mAuthController.mCurrentDialog);
     }
 
     // Corner case tests
 
     @Test
     public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         verify(mDialog1).show(any(), any());
 
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
 
         // First dialog should be dismissed without animation
         verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
@@ -312,7 +307,7 @@
 
     @Test
     public void testConfigurationPersists_whenOnConfigurationChanged() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         verify(mDialog1).show(any(), any());
 
         // Return that the UI is in "showing" state
@@ -342,7 +337,7 @@
 
     @Test
     public void testConfigurationPersists_whenBiometricFallbackToCredential() {
-        showDialog(Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC,
+        showDialog(Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK,
                 BiometricPrompt.TYPE_FACE);
         verify(mDialog1).show(any(), any());
 
@@ -361,14 +356,14 @@
         // Check that the new dialog was initialized to the credential UI.
         ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
         verify(mDialog2).show(any(), captor.capture());
-        assertEquals(Authenticator.TYPE_CREDENTIAL,
+        assertEquals(Authenticators.DEVICE_CREDENTIAL,
                 mAuthController.mLastBiometricPromptBundle
                         .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
     }
 
     @Test
     public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
 
         List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
         ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
@@ -388,21 +383,21 @@
 
     @Test
     public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
         mAuthController.onTryAgainPressed();
     }
 
     @Test
     public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
         mAuthController.onDeviceCredentialPressed();
     }
 
     @Test
     public void testActionCloseSystemDialogs_dismissesDialogIfShowing() throws Exception {
-        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        showDialog(Authenticators.BIOMETRIC_WEAK, BiometricPrompt.TYPE_FACE);
         Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         mAuthController.mBroadcastReceiver.onReceive(mContext, intent);
         waitForIdleSync();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 2ccecec..2bf855a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -261,7 +261,7 @@
 
     @Test
     public void testRemoveBubble_withDismissedNotif() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
@@ -304,7 +304,7 @@
         assertFalse(mBubbleController.isStackExpanded());
 
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // We should have bubbles & their notifs should not be suppressed
@@ -334,8 +334,8 @@
     @Test
     public void testCollapseAfterChangingExpandedBubble() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow2.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
 
@@ -377,7 +377,7 @@
     @Test
     public void testExpansionRemovesShowInShadeAndDot() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // We should have bubbles & their notifs should not be suppressed
@@ -403,7 +403,7 @@
     @Test
     public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // We should have bubbles & their notifs should not be suppressed
@@ -439,8 +439,8 @@
     @Test
     public void testRemoveLastExpandedCollapses() {
         // Mark it as a bubble and add it explicitly
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        mEntryListener.onPendingEntryAdded(mRow2.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow2.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
@@ -483,7 +483,7 @@
                 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion shouldn't change
@@ -501,7 +501,7 @@
                 Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion should change
@@ -519,7 +519,7 @@
                 Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
 
         // Add the suppress notif bubble
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // Notif should be suppressed because we were foreground
@@ -564,7 +564,7 @@
     public void testExpandStackAndSelectBubble_removedFirst() {
         final String key = mRow.getEntry().getKey();
 
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         // Simulate notification cancellation.
@@ -576,7 +576,7 @@
 
     @Test
     public void testMarkNewNotificationAsShowInShade() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
                 mRow.getEntry().getKey()));
 
@@ -586,7 +586,7 @@
 
     @Test
     public void testAddNotif_notBubble() {
-        mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
+        mEntryListener.onNotificationAdded(mNonBubbleNotifRow.getEntry());
         mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
 
         verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean());
@@ -631,7 +631,7 @@
 
     @Test
     public void testRemoveBubble_succeeds_appCancel() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
@@ -646,7 +646,7 @@
 
     @Test
     public void removeBubble_fails_clearAll()  {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
@@ -669,7 +669,7 @@
 
     @Test
     public void removeBubble_fails_userDismissNotif() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
@@ -692,7 +692,7 @@
 
     @Test
     public void removeBubble_succeeds_userDismissBubble_userDimissNotif() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mEntryListener.onNotificationAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index f2665ef..775acdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -33,6 +33,7 @@
 
 import android.app.AlarmManager;
 import android.database.ContentObserver;
+import android.hardware.Sensor;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -80,6 +81,8 @@
     private TriggerSensor mTriggerSensor;
     @Mock
     private DozeLog mDozeLog;
+    @Mock
+    private Sensor mProximitySensor;
     private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
     private TestableLooper mTestableLooper;
     private DozeSensors mDozeSensors;
@@ -90,6 +93,7 @@
         mTestableLooper = TestableLooper.get(this);
         when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
         when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProximitySensor);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
             return null;
@@ -98,6 +102,14 @@
     }
 
     @Test
+    public void testRegisterProx() {
+        // We should not register with the sensor manager initially.
+        verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
+        mDozeSensors.setProxListening(true);
+        verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt());
+    }
+
+    @Test
     public void testSensorDebounce() {
         mDozeSensors.setListening(true);
 
@@ -116,6 +128,7 @@
 
     @Test
     public void testSetListening_firstTrue_registerSettingsObserver() {
+        verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
         mDozeSensors.setListening(true);
 
         verify(mTriggerSensor).registerSettingsObserver(any(ContentObserver.class));
@@ -123,6 +136,7 @@
 
     @Test
     public void testSetListening_twiceTrue_onlyRegisterSettingsObserverOnce() {
+        verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
         mDozeSensors.setListening(true);
         mDozeSensors.setListening(true);
 
@@ -131,6 +145,7 @@
 
     @Test
     public void testSetPaused_doesntPause_sensors() {
+        verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
         mDozeSensors.setListening(true);
         verify(mTriggerSensor).setListening(eq(true));
 
@@ -147,8 +162,7 @@
 
         TestableDozeSensors() {
             super(getContext(), mAlarmManager, mSensorManager, mDozeParameters,
-                    mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback,
-                    mAlwaysOnDisplayPolicy, mDozeLog);
+                    mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog);
             for (TriggerSensor sensor : mSensors) {
                 if (sensor instanceof PluginSensor
                         && ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 8e6f4d7..d580234 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -27,7 +27,8 @@
 import android.app.NotificationManager;
 import android.os.Handler;
 import android.os.UserHandle;
-import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -35,8 +36,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.NotificationListener.NotifServiceListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,52 +51,39 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
 
-    @Mock private NotificationListenerService.RankingMap mRanking;
-
-    // Dependency mocks:
-    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotifServiceListener mServiceListener;
     @Mock private NotificationManager mNotificationManager;
-    @Mock private NotificationGroupManager mNotificationGroupManager;
 
     private NotificationListener mListener;
     private StatusBarNotification mSbn;
+    private RankingMap mRanking = new RankingMap(new Ranking[0]);
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
 
-        mListener = new NotificationListener(mContext,
-                new Handler(TestableLooper.get(this).getLooper()), mEntryManager,
-                mNotificationGroupManager);
+        mListener = new NotificationListener(
+                mContext,
+                mNotificationManager,
+                new Handler(TestableLooper.get(this).getLooper()));
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
                 new Notification(), UserHandle.CURRENT, null, 0);
+
+        mListener.addNotificationListener(mServiceListener);
     }
 
     @Test
     public void testNotificationAddCallsAddNotification() {
         mListener.onNotificationPosted(mSbn, mRanking);
         TestableLooper.get(this).processAllMessages();
-        verify(mEntryManager).addNotification(mSbn, mRanking);
-    }
-
-    @Test
-    public void testNotificationUpdateCallsUpdateNotification() {
-        when(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()))
-                .thenReturn(
-                        new NotificationEntryBuilder()
-                                .setSbn(mSbn)
-                                .build());
-        mListener.onNotificationPosted(mSbn, mRanking);
-        TestableLooper.get(this).processAllMessages();
-        verify(mEntryManager).updateNotification(mSbn, mRanking);
+        verify(mServiceListener).onNotificationPosted(mSbn, mRanking);
     }
 
     @Test
     public void testNotificationRemovalCallsRemoveNotification() {
         mListener.onNotificationRemoved(mSbn, mRanking);
         TestableLooper.get(this).processAllMessages();
-        verify(mEntryManager).removeNotification(eq(mSbn.getKey()), eq(mRanking), anyInt());
+        verify(mServiceListener).onNotificationRemoved(eq(mSbn), eq(mRanking), anyInt());
     }
 
     @Test
@@ -104,7 +91,7 @@
         mListener.onNotificationRankingUpdate(mRanking);
         TestableLooper.get(this).processAllMessages();
         // RankingMap may be modified by plugins.
-        verify(mEntryManager).updateNotificationRanking(any());
+        verify(mServiceListener).onNotificationRankingUpdate(any());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index ba7b2e2..cef210c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -26,12 +26,14 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
+import android.app.Notification;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -198,11 +200,13 @@
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0);
 
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
         NotificationEntry entry = new NotificationEntryBuilder()
                 .setImportance(IMPORTANCE_LOW)
+                .setNotification(notification)
                 .build();
         entry.setBucket(BUCKET_SILENT);
-        entry.setIsHighPriority(true);
         assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(entry));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
index d003b99..5310dd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -76,6 +76,7 @@
         mSmartActions = copyList(ranking.getSmartActions());
         mSmartReplies = copyList(ranking.getSmartReplies());
         mCanBubble = ranking.canBubble();
+        mIsVisuallyInterruptive = ranking.visuallyInterruptive();
     }
 
     public Ranking build() {
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
new file mode 100644
index 0000000..721bbdc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.NotificationEntryBuilder;
+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/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index e1beb34..8d9537d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -99,7 +99,7 @@
 
         // Capture the listener object that the collection registers with the listener service so
         // we can simulate listener service events in tests below
-        verify(mListenerService).setDownstreamListener(mListenerCaptor.capture());
+        verify(mListenerService).addNotificationListener(mListenerCaptor.capture());
         mServiceListener = checkNotNull(mListenerCaptor.getValue());
 
         mNoMan = new NoManSimulator(mServiceListener);
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 536aeb4..17d556d 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,6 +21,8 @@
 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;
@@ -91,6 +93,44 @@
     }
 
     @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 cda1538e..1764bef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -17,17 +17,12 @@
 package com.android.systemui.statusbar.notification.collection
 
 import android.app.Notification
-import android.app.NotificationChannel
 import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_HIGH
 import android.app.NotificationManager.IMPORTANCE_LOW
 import android.service.notification.NotificationListenerService.RankingMap
-import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
-
-import org.junit.runner.RunWith
-
 import androidx.test.filters.SmallTest
-
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.NotificationEntryBuilder
 import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking
@@ -42,12 +37,9 @@
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import dagger.Lazy
 import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
-
 import org.junit.Before
 import org.junit.Test
-import org.mockito.Mockito.`when`
+import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
 
 @SmallTest
@@ -76,106 +68,34 @@
     }
 
     @Test
-    fun testPeopleNotification_isHighPriority() {
-        val notification = Notification.Builder(mContext, "test")
-                .build()
-
-        val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
-                notification, mContext.user, "", 0)
-
-        `when`(personNotificationIdentifier.isPeopleNotification(sbn)).thenReturn(true)
-
-        val e = NotificationEntryBuilder()
-                .setNotification(notification)
-                .setSbn(sbn)
-                .build()
-
-        assertTrue(rankingManager.isHighPriority2(e))
-    }
-
-    @Test
-    fun lowForegroundHighPriority() {
-        val notification = mock(Notification::class.java)
-        `when`(notification.isForegroundService).thenReturn(true)
-
-        val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
-                notification, mContext.user, "", 0)
-
-        val e = NotificationEntryBuilder()
-                .setNotification(notification)
-                .setSbn(sbn)
-                .build()
-
-        modifyRanking(e)
-                .setImportance(IMPORTANCE_LOW)
-                .build()
-
-        assertTrue(rankingManager.isHighPriority2(e))
-    }
-
-    @Test
-    fun userChangeTrumpsHighPriorityCharacteristics() {
-        val notification = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
-                .build()
-
-        val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
-                notification, mContext.user, "", 0)
-
-        `when`(personNotificationIdentifier.isPeopleNotification(sbn)).thenReturn(true)
-
-        val channel = NotificationChannel("a", "a", IMPORTANCE_LOW)
-        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE)
-
-        val e = NotificationEntryBuilder()
-                .setSbn(sbn)
-                .setChannel(channel)
-                .build()
-
-        assertFalse(rankingManager.isHighPriority2(e))
-    }
-
-    @Test
     fun testSort_highPriorityTrumpsNMSRank() {
         // NMS rank says A and then B. But A is not high priority and B is, so B should sort in
         // front
-        val aN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
         val a = NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_LOW) // low priority
+                .setRank(1) // NMS says rank first
                 .setPkg("pkg")
                 .setOpPkg("pkg")
                 .setTag("tag")
-                .setNotification(aN)
+                .setNotification(
+                        Notification.Builder(mContext, "test")
+                                .build())
                 .setUser(mContext.getUser())
                 .setOverrideGroupKey("")
                 .build()
 
-        a.setIsHighPriority(false)
-
-        modifyRanking(a)
-                .setImportance(IMPORTANCE_LOW)
-                .setRank(1)
-                .build()
-
-        val bN = Notification.Builder(mContext, "test")
-                .setStyle(Notification.MessagingStyle(""))
-                .build()
         val b = NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_HIGH) // high priority
+                .setRank(2) // NMS says rank second
                 .setPkg("pkg2")
                 .setOpPkg("pkg2")
                 .setTag("tag")
-                .setNotification(bN)
+                .setNotification(
+                        Notification.Builder(mContext, "test")
+                                .build())
                 .setUser(mContext.getUser())
                 .setOverrideGroupKey("")
                 .build()
-        b.setIsHighPriority(true)
-
-        modifyRanking(b)
-                .setImportance(IMPORTANCE_LOW)
-                .setRank(2)
-                .build()
 
         assertEquals(
                 listOf(b, a),
@@ -189,6 +109,8 @@
                 .setStyle(Notification.MessagingStyle(""))
                 .build()
         val a = NotificationEntryBuilder()
+                .setRank(1)
+                .setImportance(IMPORTANCE_HIGH)
                 .setPkg("pkg")
                 .setOpPkg("pkg")
                 .setTag("tag")
@@ -196,17 +118,13 @@
                 .setUser(mContext.getUser())
                 .setOverrideGroupKey("")
                 .build()
-        a.setIsHighPriority(false)
-
-        modifyRanking(a)
-                .setImportance(IMPORTANCE_LOW)
-                .setRank(1)
-                .build()
 
         val bN = Notification.Builder(mContext, "test")
                 .setStyle(Notification.MessagingStyle(""))
                 .build()
         val b = NotificationEntryBuilder()
+                .setRank(2)
+                .setImportance(IMPORTANCE_HIGH)
                 .setPkg("pkg2")
                 .setOpPkg("pkg2")
                 .setTag("tag")
@@ -214,12 +132,6 @@
                 .setUser(mContext.getUser())
                 .setOverrideGroupKey("")
                 .build()
-        b.setIsHighPriority(false)
-
-        modifyRanking(b)
-                .setImportance(IMPORTANCE_LOW)
-                .setRank(2)
-                .build()
 
         assertEquals(
                 listOf(a, b),
@@ -239,7 +151,7 @@
                 .setOverrideGroupKey("")
                 .build()
 
-        modifyRanking(e).setImportance(IMPORTANCE_DEFAULT) .build()
+        modifyRanking(e).setImportance(IMPORTANCE_DEFAULT).build()
 
         rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test")
         assertEquals(e.bucket, BUCKET_ALERTING)
@@ -281,11 +193,6 @@
         sectionsFeatureManager,
         peopleNotificationIdentifier
     ) {
-
-        fun isHighPriority2(e: NotificationEntry): Boolean {
-            return isHighPriority(e)
-        }
-
         fun applyTestRankingMap(r: RankingMap) {
             rankingMap = r
         }
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
new file mode 100644
index 0000000..11488a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class IsHighPriorityProviderTest extends SysuiTestCase {
+    private IsHighPriorityProvider mIsHighPriorityProvider;
+
+    @Before
+    public void setup() {
+        mIsHighPriorityProvider = new IsHighPriorityProvider();
+    }
+
+    @Test
+    public void testCache() {
+        // GIVEN a notification with high importance
+        final NotificationEntry entryHigh = new NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
+
+        // GIVEN notification with min importance
+        final NotificationEntry entryMin = new NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_MIN)
+                .build();
+
+        // WHEN we get the value for the high priority entry
+        assertTrue(mIsHighPriorityProvider.get(entryHigh));
+
+        // THEN the value is cached, so even when passed an entryMin, we still get high priority
+        assertTrue(mIsHighPriorityProvider.get(entryMin));
+
+        // UNTIL the provider is invalidated
+        mIsHighPriorityProvider.invalidate();
+
+        // THEN the priority is recalculated
+        assertFalse(mIsHighPriorityProvider.get(entryMin));
+    }
+
+    @Test
+    public void highImportance() {
+        // GIVEN notification has high importance
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
+
+        // THEN it has high priority
+        assertTrue(mIsHighPriorityProvider.get(entry));
+    }
+
+    @Test
+    public void peopleNotification() {
+        // GIVEN notification is low importance but has a person associated with it
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .addPerson(
+                        new Person.Builder()
+                                .setName("name")
+                                .setKey("abc")
+                                .setUri("uri")
+                                .setBot(true)
+                                .build())
+                .build();
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+
+        // THEN it has high priority
+        assertTrue(mIsHighPriorityProvider.get(entry));
+    }
+
+    @Test
+    public void messagingStyle() {
+        // GIVEN notification is low importance but has messaging style
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle(""))
+                .build();
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .build();
+
+        // THEN it has high priority
+        assertTrue(mIsHighPriorityProvider.get(entry));
+    }
+
+    @Test
+    public void lowImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+
+        // THEN it has high priority
+        assertTrue(mIsHighPriorityProvider.get(entry));
+    }
+
+    @Test
+    public void minImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_MIN)
+                .build();
+
+        // THEN it does NOT have high priority
+        assertFalse(mIsHighPriorityProvider.get(entry));
+    }
+
+    @Test
+    public void userChangeTrumpsHighPriorityCharacteristics() {
+        // GIVEN notification has high priority characteristics but the user changed the importance
+        // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .addPerson(
+                        new Person.Builder()
+                                .setName("name")
+                                .setKey("abc")
+                                .setUri("uri")
+                                .setBot(true)
+                                .build())
+                .setStyle(new Notification.MessagingStyle(""))
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .build();
+
+        final NotificationChannel channel = new NotificationChannel("a", "a",
+                IMPORTANCE_LOW);
+        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setChannel(channel)
+                .build();
+
+        // THEN it does NOT have high priority
+        assertFalse(mIsHighPriorityProvider.get(entry));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
index f5d6f22..0764d0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
@@ -89,8 +89,17 @@
                 return mockSubscription
             }
         }
+        val fakeSettingDataSource = object : DataSource<Boolean> {
+            override fun registerListener(listener: DataListener<Boolean>): Subscription {
+                listener.onDataChanged(true)
+                return mockSubscription
+            }
+        }
         val factoryDataSource = PeopleHubViewModelFactoryDataSourceImpl(
-                mockActivityStarter, fakeModelDataSource)
+                mockActivityStarter,
+                fakeModelDataSource,
+                fakeSettingDataSource
+        )
         val fakeListener = FakeDataListener<PeopleHubViewModelFactory>()
         val mockClickView = mock(View::class.java)
 
@@ -112,6 +121,41 @@
                 same(mockClickView)
         )
     }
+
+    @Test
+    fun testViewModelDataSource_notVisibleIfSettingDisabled() {
+        val fakeClickIntent = PendingIntent.getActivity(context, 0, Intent("action"), 0)
+        val fakePerson = fakePersonModel("id", "name", fakeClickIntent)
+        val fakeModel = PeopleHubModel(listOf(fakePerson))
+        val mockSubscription = mock(Subscription::class.java)
+        val fakeModelDataSource = object : DataSource<PeopleHubModel> {
+            override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
+                listener.onDataChanged(fakeModel)
+                return mockSubscription
+            }
+        }
+        val fakeSettingDataSource = object : DataSource<Boolean> {
+            override fun registerListener(listener: DataListener<Boolean>): Subscription {
+                listener.onDataChanged(false)
+                return mockSubscription
+            }
+        }
+        val factoryDataSource = PeopleHubViewModelFactoryDataSourceImpl(
+                mockActivityStarter,
+                fakeModelDataSource,
+                fakeSettingDataSource
+        )
+        val fakeListener = FakeDataListener<PeopleHubViewModelFactory>()
+        val mockClickView = mock(View::class.java)
+
+        factoryDataSource.registerListener(fakeListener)
+
+        val viewModel = (fakeListener.lastSeen as Maybe.Just).value
+                .createWithAssociatedClickView(mockClickView)
+        assertThat(viewModel.isVisible).isFalse()
+        val people = viewModel.people.toList()
+        assertThat(people.size).isEqualTo(0)
+    }
 }
 
 /** Works around Mockito matchers returning `null` and breaking non-nullable Kotlin code. */
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 43d39a2..ccc9496 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
@@ -20,6 +20,7 @@
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -383,15 +384,14 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(true);
-        modifyRanking(row.getEntry())
+        final NotificationEntry entry = row.getEntry();
+        modifyRanking(entry)
                 .setUserSentiment(USER_SENTIMENT_NEGATIVE)
-                .setImportance(IMPORTANCE_DEFAULT)
+                .setImportance(IMPORTANCE_HIGH)
                 .build();
-        row.getEntry().setIsHighPriority(true);
-        when(row.getIsNonblockable()).thenReturn(false);
-        StatusBarNotification statusBarNotification = row.getEntry().getSbn();
-        NotificationEntry entry = row.getEntry();
 
+        when(row.getIsNonblockable()).thenReturn(false);
+        StatusBarNotification statusBarNotification = entry.getSbn();
         mGutsManager.initializeNotificationInfo(row, notificationInfoView);
 
         verify(notificationInfoView).bindNotification(
@@ -408,7 +408,7 @@
                 eq(false),
                 eq(false),
                 eq(true) /* isForBlockingHelper */,
-                eq(IMPORTANCE_DEFAULT),
+                eq(IMPORTANCE_HIGH),
                 eq(true) /* wasShownHighPriority */);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4d6ff1f..008a349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -760,6 +760,17 @@
     }
 
     @Test
+    public void testWillHideDockedWallpaper() {
+        mAlwaysOnEnabled = false;
+        when(mDockManager.isDocked()).thenReturn(true);
+        mScrimController.setWallpaperSupportsAmbientMode(true);
+
+        mScrimController.transitionTo(ScrimState.AOD);
+
+        verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any());
+    }
+
+    @Test
     public void testConservesExpansionOpacityAfterTransition() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.setPanelExpansion(0.5f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
new file mode 100644
index 0000000..a39fbc4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -0,0 +1,436 @@
+package com.android.systemui.util.animation
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.util.ArrayMap
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringForce
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.animation.PhysicsAnimator.EndListener
+import com.android.systemui.util.animation.PhysicsAnimator.UpdateListener
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class PhysicsAnimatorTest : SysuiTestCase() {
+    private lateinit var viewGroup: ViewGroup
+    private lateinit var testView: View
+    private lateinit var testView2: View
+
+    private lateinit var animator: PhysicsAnimator<View>
+
+    private val springConfig = PhysicsAnimator.SpringConfig(
+            SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY)
+    private val flingConfig = PhysicsAnimator.FlingConfig(2f)
+
+    private lateinit var mockUpdateListener: UpdateListener<View>
+    private lateinit var mockEndListener: EndListener<View>
+    private lateinit var mockEndAction: Runnable
+
+    private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        mockUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+        mockEndListener = mock(EndListener::class.java) as EndListener<View>
+        mockEndAction = mock(Runnable::class.java)
+
+        viewGroup = FrameLayout(context)
+        testView = View(context)
+        testView2 = View(context)
+        viewGroup.addView(testView)
+        viewGroup.addView(testView2)
+
+        PhysicsAnimatorTestUtils.prepareForTest()
+
+        // Most of our tests involve checking the end state of animations, so we want calls that
+        // start animations to block the test thread until the animations have ended.
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(true)
+
+        animator = PhysicsAnimator.getInstance(testView)
+    }
+
+    @After
+    fun tearDown() {
+        PhysicsAnimatorTestUtils.tearDown()
+    }
+
+    @Test
+    fun testOneAnimatorPerView() {
+        assertEquals(animator, PhysicsAnimator.getInstance(testView))
+        assertEquals(PhysicsAnimator.getInstance(testView), PhysicsAnimator.getInstance(testView))
+        assertNotEquals(animator, PhysicsAnimator.getInstance(testView2))
+    }
+
+    @Test
+    fun testSpringOneProperty() {
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 50f, springConfig)
+                .start()
+
+        assertEquals(testView.translationX, 50f, 1f)
+    }
+
+    @Test
+    fun testSpringMultipleProperties() {
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+                .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+                .spring(DynamicAnimation.SCALE_Y, 1.1f, springConfig)
+                .start()
+
+        assertEquals(10f, testView.translationX, 1f)
+        assertEquals(50f, testView.translationY, 1f)
+        assertEquals(1.1f, testView.scaleY, 0.01f)
+    }
+
+    @Test
+    fun testFling() {
+        val startTime = System.currentTimeMillis()
+
+        animator
+                .fling(DynamicAnimation.TRANSLATION_X, 1000f /* startVelocity */, flingConfig)
+                .fling(DynamicAnimation.TRANSLATION_Y, 500f, flingConfig)
+                .start()
+
+        val elapsedTimeSeconds = (System.currentTimeMillis() - startTime) / 1000f
+
+        // If the fling worked, the view should be somewhere between its starting position and the
+        // and the theoretical no-friction maximum of startVelocity (in pixels per second)
+        // multiplied by elapsedTimeSeconds. We can't calculate an exact expected location for a
+        // fling, so this is close enough.
+        assertTrue(testView.translationX > 0f)
+        assertTrue(testView.translationX < 1000f * elapsedTimeSeconds)
+        assertTrue(testView.translationY > 0f)
+        assertTrue(testView.translationY < 500f * elapsedTimeSeconds)
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun testEndListenersAndActions() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+                .spring(DynamicAnimation.TRANSLATION_Y, 500f, springConfig)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+        // Once TRANSLATION_X is done, the view should be at x = 10...
+        assertEquals(10f, testView.translationX, 1f)
+
+        // / ...TRANSLATION_Y should still be running...
+        assertTrue(animator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+
+        // ...and our end listener should have been called with x = 10, velocity = 0, and allEnded =
+        // false since TRANSLATION_Y is still running.
+        verify(mockEndListener).onAnimationEnd(
+                testView,
+                DynamicAnimation.TRANSLATION_X,
+                canceled = false,
+                finalValue = 10f,
+                finalVelocity = 0f,
+                allRelevantPropertyAnimsEnded = false)
+        verifyNoMoreInteractions(mockEndListener)
+
+        // The end action should not have been run yet.
+        verify(mockEndAction, times(0)).run()
+
+        // Block until TRANSLATION_Y finishes.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+        // The view should have been moved.
+        assertEquals(10f, testView.translationX, 1f)
+        assertEquals(500f, testView.translationY, 1f)
+
+        // The end listener should have been called, this time with TRANSLATION_Y, y = 50, and
+        // allEnded = true.
+        verify(mockEndListener).onAnimationEnd(
+                testView,
+                DynamicAnimation.TRANSLATION_Y,
+                canceled = false,
+                finalValue = 500f,
+                finalVelocity = 0f,
+                allRelevantPropertyAnimsEnded = true)
+        verifyNoMoreInteractions(mockEndListener)
+
+        // Now that all properties are done animating, the end action should have been called.
+        verify(mockEndAction, times(1)).run()
+    }
+
+    @Test
+    fun testUpdateListeners() {
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 100f, springConfig)
+                .spring(DynamicAnimation.TRANSLATION_Y, 50f, springConfig)
+                .addUpdateListener(object : UpdateListener<View> {
+                    override fun onAnimationUpdateForProperty(
+                        target: View,
+                        values: UpdateMap<View>
+                    ) {
+                        mockUpdateListener.onAnimationUpdateForProperty(target, values)
+                    }
+                })
+                .start()
+
+        verifyUpdateListenerCalls(animator, mockUpdateListener)
+    }
+
+    @Test
+    fun testListenersNotCalledOnSubsequentAnimations() {
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 10f, springConfig)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .withEndActions(mockEndAction::run)
+                .start()
+
+        verifyUpdateListenerCalls(animator, mockUpdateListener)
+        verify(mockEndListener, times(1)).onAnimationEnd(
+                eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
+                eq(true))
+        verify(mockEndAction, times(1)).run()
+
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 0f, springConfig)
+                .start()
+
+        // We didn't pass any of the listeners/actions to the subsequent animation, so they should
+        // never have been called.
+        verifyNoMoreInteractions(mockUpdateListener)
+        verifyNoMoreInteractions(mockEndListener)
+        verifyNoMoreInteractions(mockEndAction)
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun testAnimationsUpdatedWhileInMotion() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+        // Spring towards x = 100f.
+        animator
+                .spring(
+                        DynamicAnimation.TRANSLATION_X,
+                        100f,
+                        springConfig)
+                .start()
+
+        // Block until it reaches x = 50f.
+        PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+                animator) { view -> view.translationX > 50f }
+
+        // Translation X value at the time of reversing the animation to spring to x = 0f.
+        val reversalTranslationX = testView.translationX
+
+        // Spring back towards 0f.
+        animator
+                .spring(
+                        DynamicAnimation.TRANSLATION_X,
+                        0f,
+                        // Lower the stiffness to ensure the update listener receives at least one
+                        // update frame where the view has continued to move to the right.
+                        springConfig.apply { stiffness = SpringForce.STIFFNESS_LOW })
+                .start()
+
+        // Wait for TRANSLATION_X.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+        // Verify that the animation continued past the X value at the time of reversal, before
+        // springing back. This ensures the change in direction was not abrupt.
+        verifyAnimationUpdateFrames(
+                animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value > reversalTranslationX },
+                { u -> u.value < reversalTranslationX })
+
+        // Verify that the view is where it should be.
+        assertEquals(0f, testView.translationX, 1f)
+    }
+
+    @Test
+    @Throws(InterruptedException::class)
+    fun testAnimationsUpdatedWhileInMotion_originalListenersStillCalled() {
+        PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+        // Spring TRANSLATION_X to 100f, with an update and end listener provided.
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 100f, springConfig)
+                .addUpdateListener(mockUpdateListener)
+                .addEndListener(mockEndListener)
+                .start()
+
+        // Wait until the animation is halfway there.
+        PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+                animator) { view -> view.translationX > 50f }
+
+        // The end listener shouldn't have been called since the animation hasn't ended.
+        verifyNoMoreInteractions(mockEndListener)
+
+        // Make sure we called the update listener with appropriate values.
+        verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value > 0f },
+                { u -> u.value >= 50f })
+
+        // Mock a second end listener.
+        val secondEndListener = mock(EndListener::class.java) as EndListener<View>
+        val secondUpdateListener = mock(UpdateListener::class.java) as UpdateListener<View>
+
+        // Start a new animation that springs both TRANSLATION_X and TRANSLATION_Y, and provide it
+        // the second end listener. This new end listener should be called for the end of
+        // TRANSLATION_X and TRANSLATION_Y, with allEnded = true when both have ended.
+        animator
+                .spring(DynamicAnimation.TRANSLATION_X, 200f, springConfig)
+                .spring(DynamicAnimation.TRANSLATION_Y, 4000f, springConfig)
+                .addUpdateListener(secondUpdateListener)
+                .addEndListener(secondEndListener)
+                .start()
+
+        // Wait for TRANSLATION_X to end.
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_X)
+
+        // The update listener provided to the initial animation call (the one that only animated
+        // TRANSLATION_X) should have been called with values on the way to x = 200f. This is
+        // because the second animation call updated the original TRANSLATION_X animation.
+        verifyAnimationUpdateFrames(
+                animator, DynamicAnimation.TRANSLATION_X,
+                { u -> u.value > 100f }, { u -> u.value >= 200f })
+
+        // The original end listener should also have been called, with allEnded = true since it was
+        // provided to an animator that animated only TRANSLATION_X.
+        verify(mockEndListener, times(1))
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+        verifyNoMoreInteractions(mockEndListener)
+
+        // The second end listener should have been called, but with allEnded = false since it was
+        // provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
+        verify(secondEndListener, times(1))
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+        verifyNoMoreInteractions(secondEndListener)
+
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
+
+        // The original end listener shouldn't receive any callbacks because it was not provided to
+        // an animator that animated TRANSLATION_Y.
+        verifyNoMoreInteractions(mockEndListener)
+
+        verify(secondEndListener, times(1))
+                .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+        verifyNoMoreInteractions(secondEndListener)
+    }
+
+    @Test
+    fun testFlingRespectsMinMax() {
+        animator
+                .fling(DynamicAnimation.TRANSLATION_X,
+                        startVelocity = 1000f,
+                        friction = 1.1f,
+                        max = 10f)
+                .addEndListener(mockEndListener)
+                .start()
+
+        // Ensure that the view stopped at x = 10f, and the end listener was called once with that
+        // value.
+        assertEquals(10f, testView.translationX, 1f)
+        verify(mockEndListener, times(1))
+                .onAnimationEnd(
+                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
+                        anyFloat(), eq(true))
+
+        animator
+                .fling(
+                        DynamicAnimation.TRANSLATION_X,
+                        startVelocity = -1000f,
+                        friction = 1.1f,
+                        min = -5f)
+                .addEndListener(mockEndListener)
+                .start()
+
+        // Ensure that the view stopped at x = -5f, and the end listener was called once with that
+        // value.
+        assertEquals(-5f, testView.translationX, 1f)
+        verify(mockEndListener, times(1))
+                .onAnimationEnd(
+                        eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
+                        anyFloat(), eq(true))
+    }
+
+    @Test
+    fun testExtensionProperty() {
+        testView
+                .physicsAnimator
+                .spring(DynamicAnimation.TRANSLATION_X, 200f)
+                .start()
+
+        assertEquals(200f, testView.translationX, 1f)
+    }
+
+    /**
+     * Verifies that the calls to the mock update listener match the animation update frames
+     * reported by the test internal listener, in order.
+     */
+    private fun <T : Any> verifyUpdateListenerCalls(
+        animator: PhysicsAnimator<T>,
+        mockUpdateListener: UpdateListener<T>
+    ) {
+        val updates = getAnimationUpdateFrames(animator)
+
+        for (invocation in Mockito.mockingDetails(mockUpdateListener).invocations) {
+
+            // Grab the update map of Property -> AnimationUpdate that was passed to the mock update
+            // listener.
+            val updateMap = invocation.arguments[1]
+                    as ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
+
+            //
+            for ((property, update) in updateMap) {
+                val updatesForProperty = updates[property]!!
+
+                // This update should be the next one in the list for this property.
+                if (update != updatesForProperty[0]) {
+                    Assert.fail("The update listener was called with an unexpected value: $update.")
+                }
+
+                updatesForProperty.remove(update)
+            }
+
+            // Mark this invocation verified.
+            verify(mockUpdateListener).onAnimationUpdateForProperty(animator.target, updateMap)
+        }
+
+        verifyNoMoreInteractions(mockUpdateListener)
+
+        // Since we were removing values as matching invocations were found, there should no longer
+        // be any values remaining. If there are, it means the update listener wasn't notified when
+        // it should have been.
+        assertEquals(0,
+                updates.values.fold(0, { count, propertyUpdates -> count + propertyUpdates.size }))
+
+        clearAnimationUpdateFrames(animator)
+    }
+}
\ No newline at end of file
diff --git a/services/Android.bp b/services/Android.bp
index 8376d2b..8be6e16 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -117,9 +117,6 @@
         " --hide ReferencesHidden" +
         " --hide DeprecationMismatch" +
         " --hide HiddenTypedefConstant",
-    libs: [
-        "framework-all",
-    ],
     visibility: ["//visibility:private"],
 }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
deleted file mode 100644
index 3dfe59e..0000000
--- a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
+++ /dev/null
@@ -1,640 +0,0 @@
-/*
- ** Copyright 2015, The Android Open Source Project
- **
- ** Licensed under the Apache License, Version 2.0 (the "License");
- ** you may not use this file except in compliance with the License.
- ** You may obtain a copy of the License at
- **
- **     http://www.apache.org/licenses/LICENSE-2.0
- **
- ** Unless required by applicable law or agreed to in writing, software
- ** distributed under the License is distributed on an "AS IS" BASIS,
- ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ** See the License for the specific language governing permissions and
- ** limitations under the License.
- */
-
-package com.android.server.accessibility.gestures;
-
-import android.accessibilityservice.AccessibilityGestureEvent;
-import android.accessibilityservice.AccessibilityService;
-import android.content.Context;
-import android.gesture.GesturePoint;
-import android.graphics.PointF;
-import android.util.Slog;
-import android.util.TypedValue;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-
-/**
- * This class handles gesture detection for the Touch Explorer.  It collects
- * touch events and determines when they match a gesture, as well as when they
- * won't match a gesture.  These state changes are then surfaced to mListener.
- */
-class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListener {
-
-    private static final boolean DEBUG = false;
-
-    // Tag for logging received events.
-    private static final String LOG_TAG = "AccessibilityGestureDetector";
-
-    // Constants for sampling motion event points.
-    // We sample based on a minimum distance between points, primarily to improve accuracy by
-    // reducing noisy minor changes in direction.
-    private static final float MIN_INCHES_BETWEEN_SAMPLES = 0.1f;
-    private final float mMinPixelsBetweenSamplesX;
-    private final float mMinPixelsBetweenSamplesY;
-
-    // Constants for separating gesture segments
-    private static final float ANGLE_THRESHOLD = 0.0f;
-
-    // Constants for line segment directions
-    private static final int LEFT = 0;
-    private static final int RIGHT = 1;
-    private static final int UP = 2;
-    private static final int DOWN = 3;
-    private static final int[][] DIRECTIONS_TO_GESTURE_ID = {
-        {
-            AccessibilityService.GESTURE_SWIPE_LEFT,
-            AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT,
-            AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP,
-            AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN
-        },
-        {
-            AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT,
-            AccessibilityService.GESTURE_SWIPE_RIGHT,
-            AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP,
-            AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN
-        },
-        {
-            AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT,
-            AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT,
-            AccessibilityService.GESTURE_SWIPE_UP,
-            AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN
-        },
-        {
-            AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT,
-            AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT,
-            AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP,
-            AccessibilityService.GESTURE_SWIPE_DOWN
-        }
-    };
-
-
-    /**
-     * Listener functions are called as a result of onMoveEvent().  The current
-     * MotionEvent in the context of these functions is the event passed into
-     * onMotionEvent.
-     */
-    public interface Listener {
-        /**
-         * Called when the user has performed a double tap and then held down
-         * the second tap.
-         *
-         * @param event The most recent MotionEvent received.
-         * @param policyFlags The policy flags of the most recent event.
-         */
-        void onDoubleTapAndHold(MotionEvent event, int policyFlags);
-
-        /**
-         * Called when the user lifts their finger on the second tap of a double
-         * tap.
-         *
-         * @param event The most recent MotionEvent received.
-         * @param policyFlags The policy flags of the most recent event.
-         *
-         * @return true if the event is consumed, else false
-         */
-        boolean onDoubleTap(MotionEvent event, int policyFlags);
-
-        /**
-         * Called when the system has decided the event stream is a gesture.
-         *
-         * @return true if the event is consumed, else false
-         */
-        boolean onGestureStarted();
-
-        /**
-         * Called when an event stream is recognized as a gesture.
-         *
-         * @param gestureEvent Information about the gesture.
-         *
-         * @return true if the event is consumed, else false
-         */
-        boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
-
-        /**
-         * Called when the system has decided an event stream doesn't match any
-         * known gesture.
-         *
-         * @param event The most recent MotionEvent received.
-         * @param policyFlags The policy flags of the most recent event.
-         *
-         * @return true if the event is consumed, else false
-         */
-        public boolean onGestureCancelled(MotionEvent event, int policyFlags);
-    }
-
-    private final Listener mListener;
-    private final Context mContext;  // Retained for on-demand construction of GestureDetector.
-    private final GestureDetector mGestureDetector;  // Double-tap detector.
-
-    // Indicates that a single tap has occurred.
-    private boolean mFirstTapDetected;
-
-    // Indicates that the down event of a double tap has occured.
-    private boolean mDoubleTapDetected;
-
-    // Indicates that motion events are being collected to match a gesture.
-    private boolean mRecognizingGesture;
-
-    // Indicates that we've collected enough data to be sure it could be a
-    // gesture.
-    private boolean mGestureStarted;
-
-    // Indicates that motion events from the second pointer are being checked
-    // for a double tap.
-    private boolean mSecondFingerDoubleTap;
-
-    // Tracks the most recent time where ACTION_POINTER_DOWN was sent for the
-    // second pointer.
-    private long mSecondPointerDownTime;
-
-    // Policy flags of the previous event.
-    private int mPolicyFlags;
-
-    // These values track the previous point that was saved to use for gesture
-    // detection.  They are only updated when the user moves more than the
-    // recognition threshold.
-    private float mPreviousGestureX;
-    private float mPreviousGestureY;
-
-    // These values track the previous point that was used to determine if there
-    // was a transition into or out of gesture detection.  They are updated when
-    // the user moves more than the detection threshold.
-    private float mBaseX;
-    private float mBaseY;
-    private long mBaseTime;
-
-    // This is the calculated movement threshold used track if the user is still
-    // moving their finger.
-    private final float mGestureDetectionThreshold;
-
-    // Buffer for storing points for gesture detection.
-    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
-
-    // The minimal delta between moves to add a gesture point.
-    private static final int TOUCH_TOLERANCE = 3;
-
-    // The minimal score for accepting a predicted gesture.
-    private static final float MIN_PREDICTION_SCORE = 2.0f;
-
-    // Distance a finger must travel before we decide if it is a gesture or not.
-    private static final int GESTURE_CONFIRM_MM = 10;
-
-    // Time threshold used to determine if an interaction is a gesture or not.
-    // If the first movement of 1cm takes longer than this value, we assume it's
-    // a slow movement, and therefore not a gesture.
-    //
-    // This value was determined by measuring the time for the first 1cm
-    // movement when gesturing, and touch exploring.  Based on user testing,
-    // all gestures started with the initial movement taking less than 100ms.
-    // When touch exploring, the first movement almost always takes longer than
-    // 200ms.
-    private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
-
-    // Time threshold used to determine if a gesture should be cancelled.  If
-    // the finger takes more than this time to move 1cm, the ongoing gesture is
-    // cancelled.
-    private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
-
-    /**
-     * Construct the gesture detector for {@link TouchExplorer}.
-     *
-     * @see #AccessibilityGestureDetector(Context, Listener, GestureDetector)
-     */
-    AccessibilityGestureDetector(Context context, Listener listener) {
-        this(context, listener, null);
-    }
-
-    /**
-     * Construct the gesture detector for {@link TouchExplorer}.
-     *
-     * @param context A context handle for accessing resources.
-     * @param listener A listener to callback with gesture state or information.
-     * @param detector The gesture detector to handle touch event. If null the default one created
-     *                 in place, or for testing purpose.
-     */
-    AccessibilityGestureDetector(Context context, Listener listener, GestureDetector detector) {
-        mListener = listener;
-        mContext = context;
-
-        // Break the circular dependency between constructors and let the class to be testable
-        if (detector == null) {
-            mGestureDetector = new GestureDetector(context, this);
-        } else {
-            mGestureDetector = detector;
-        }
-        mGestureDetector.setOnDoubleTapListener(this);
-        mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
-                context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
-
-        // Calculate minimum gesture velocity
-        final float pixelsPerInchX = context.getResources().getDisplayMetrics().xdpi;
-        final float pixelsPerInchY = context.getResources().getDisplayMetrics().ydpi;
-        mMinPixelsBetweenSamplesX = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchX;
-        mMinPixelsBetweenSamplesY = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchY;
-    }
-
-    /**
-     * Handle a motion event.  If an action is completed, the appropriate
-     * callback on mListener is called, and the return value of the callback is
-     * passed to the caller.
-     *
-     * @param event The transformed motion event to be handled.
-     * @param rawEvent The raw motion event.  It's important that this be the raw
-     * event, before any transformations have been applied, so that measurements
-     * can be made in physical units.
-     * @param policyFlags Policy flags for the event.
-     *
-     * @return true if the event is consumed, else false
-     */
-    public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        // The accessibility gesture detector is interested in the movements in physical space,
-        // so it uses the rawEvent to ignore magnification and other transformations.
-        final float x = rawEvent.getX();
-        final float y = rawEvent.getY();
-        final long time = rawEvent.getEventTime();
-
-        mPolicyFlags = policyFlags;
-        switch (rawEvent.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mDoubleTapDetected = false;
-                mSecondFingerDoubleTap = false;
-                mRecognizingGesture = true;
-                mGestureStarted = false;
-                mPreviousGestureX = x;
-                mPreviousGestureY = y;
-                mStrokeBuffer.clear();
-                mStrokeBuffer.add(new GesturePoint(x, y, time));
-
-                mBaseX = x;
-                mBaseY = y;
-                mBaseTime = time;
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                if (mRecognizingGesture) {
-                    final float deltaX = mBaseX - x;
-                    final float deltaY = mBaseY - y;
-                    final double moveDelta = Math.hypot(deltaX, deltaY);
-                    if (moveDelta > mGestureDetectionThreshold) {
-                        // If the pointer has moved more than the threshold,
-                        // update the stored values.
-                        mBaseX = x;
-                        mBaseY = y;
-                        mBaseTime = time;
-
-                        // Since the pointer has moved, this is not a double
-                        // tap.
-                        mFirstTapDetected = false;
-                        mDoubleTapDetected = false;
-
-                        // If this hasn't been confirmed as a gesture yet, send
-                        // the event.
-                        if (!mGestureStarted) {
-                            mGestureStarted = true;
-                            return mListener.onGestureStarted();
-                        }
-                    } else if (!mFirstTapDetected) {
-                        // The finger may not move if they are double tapping.
-                        // In that case, we shouldn't cancel the gesture.
-                        final long timeDelta = time - mBaseTime;
-                        final long threshold = mGestureStarted ?
-                            CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS :
-                            CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS;
-
-                        // If the pointer hasn't moved for longer than the
-                        // timeout, cancel gesture detection.
-                        if (timeDelta > threshold) {
-                            cancelGesture();
-                            return mListener.onGestureCancelled(rawEvent, policyFlags);
-                        }
-                    }
-
-                    final float dX = Math.abs(x - mPreviousGestureX);
-                    final float dY = Math.abs(y - mPreviousGestureY);
-                    if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
-                        mPreviousGestureX = x;
-                        mPreviousGestureY = y;
-                        mStrokeBuffer.add(new GesturePoint(x, y, time));
-                    }
-                }
-                break;
-
-            case MotionEvent.ACTION_UP:
-                if (mDoubleTapDetected) {
-                    return finishDoubleTap(rawEvent, policyFlags);
-                }
-                if (mGestureStarted) {
-                    final float dX = Math.abs(x - mPreviousGestureX);
-                    final float dY = Math.abs(y - mPreviousGestureY);
-                    if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
-                        mStrokeBuffer.add(new GesturePoint(x, y, time));
-                    }
-                    return recognizeGesture(rawEvent, policyFlags);
-                }
-                break;
-
-            case MotionEvent.ACTION_POINTER_DOWN:
-                // Once a second finger is used, we're definitely not
-                // recognizing a gesture.
-                cancelGesture();
-
-                if (rawEvent.getPointerCount() == 2) {
-                    // If this was the second finger, attempt to recognize double
-                    // taps on it.
-                    mSecondFingerDoubleTap = true;
-                    mSecondPointerDownTime = time;
-                } else {
-                    // If there are more than two fingers down, stop watching
-                    // for a double tap.
-                    mSecondFingerDoubleTap = false;
-                }
-                break;
-
-            case MotionEvent.ACTION_POINTER_UP:
-                // If we're detecting taps on the second finger, see if we
-                // should finish the double tap.
-                if (mSecondFingerDoubleTap && mDoubleTapDetected) {
-                    return finishDoubleTap(rawEvent, policyFlags);
-                }
-                break;
-
-            case MotionEvent.ACTION_CANCEL:
-                clear();
-                break;
-        }
-
-        // If we're detecting taps on the second finger, map events from the
-        // finger to the first finger.
-        if (mSecondFingerDoubleTap) {
-            MotionEvent newEvent = mapSecondPointerToFirstPointer(rawEvent);
-            if (newEvent == null) {
-                return false;
-            }
-            boolean handled = mGestureDetector.onTouchEvent(newEvent);
-            newEvent.recycle();
-            return handled;
-        }
-
-        if (!mRecognizingGesture) {
-            return false;
-        }
-
-        // Pass the transformed event on to the standard gesture detector.
-        return mGestureDetector.onTouchEvent(event);
-    }
-
-    public void clear() {
-        mFirstTapDetected = false;
-        mDoubleTapDetected = false;
-        mSecondFingerDoubleTap = false;
-        mGestureStarted = false;
-        mGestureDetector.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_CANCEL,
-                0.0f, 0.0f, 0));
-        cancelGesture();
-    }
-
-
-    @Override
-    public void onLongPress(MotionEvent e) {
-        maybeSendLongPress(e, mPolicyFlags);
-    }
-
-    @Override
-    public boolean onSingleTapUp(MotionEvent event) {
-        mFirstTapDetected = true;
-        return false;
-    }
-
-    @Override
-    public boolean onSingleTapConfirmed(MotionEvent event) {
-        clear();
-        return false;
-    }
-
-    @Override
-    public boolean onDoubleTap(MotionEvent event) {
-        // The processing of the double tap is deferred until the finger is
-        // lifted, so that we can detect a long press on the second tap.
-        mDoubleTapDetected = true;
-        return false;
-    }
-
-    private void maybeSendLongPress(MotionEvent event, int policyFlags) {
-        if (!mDoubleTapDetected) {
-            return;
-        }
-
-        clear();
-
-        mListener.onDoubleTapAndHold(event, policyFlags);
-    }
-
-    private boolean finishDoubleTap(MotionEvent event, int policyFlags) {
-        clear();
-
-        return mListener.onDoubleTap(event, policyFlags);
-    }
-
-    private void cancelGesture() {
-        mRecognizingGesture = false;
-        mGestureStarted = false;
-        mStrokeBuffer.clear();
-    }
-
-    /**
-     * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
-     * Listener callbacks for success or failure.
-     *
-     * @param event The raw motion event to pass to the listener callbacks.
-     * @param policyFlags Policy flags for the event.
-     *
-     * @return true if the event is consumed, else false
-     */
-    private boolean recognizeGesture(MotionEvent event, int policyFlags) {
-        if (mStrokeBuffer.size() < 2) {
-            return mListener.onGestureCancelled(event, policyFlags);
-        }
-
-        // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
-        // direction change.
-        // Method: for each sampled motion event, check the angle of the most recent motion vector
-        // versus the preceding motion vector, and segment the line if the angle is about
-        // 90 degrees.
-
-        ArrayList<PointF> path = new ArrayList<>();
-        PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
-        path.add(lastDelimiter);
-
-        float dX = 0;  // Sum of unit vectors from last delimiter to each following point
-        float dY = 0;
-        int count = 0;  // Number of points since last delimiter
-        float length = 0;  // Vector length from delimiter to most recent point
-
-        PointF next = new PointF();
-        for (int i = 1; i < mStrokeBuffer.size(); ++i) {
-            next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
-            if (count > 0) {
-                // Average of unit vectors from delimiter to following points
-                float currentDX = dX / count;
-                float currentDY = dY / count;
-
-                // newDelimiter is a possible new delimiter, based on a vector with length from
-                // the last delimiter to the previous point, but in the direction of the average
-                // unit vector from delimiter to previous points.
-                // Using the averaged vector has the effect of "squaring off the curve",
-                // creating a sharper angle between the last motion and the preceding motion from
-                // the delimiter. In turn, this sharper angle achieves the splitting threshold
-                // even in a gentle curve.
-                PointF newDelimiter = new PointF(length * currentDX + lastDelimiter.x,
-                    length * currentDY + lastDelimiter.y);
-
-                // Unit vector from newDelimiter to the most recent point
-                float nextDX = next.x - newDelimiter.x;
-                float nextDY = next.y - newDelimiter.y;
-                float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
-                nextDX = nextDX / nextLength;
-                nextDY = nextDY / nextLength;
-
-                // Compare the initial motion direction to the most recent motion direction,
-                // and segment the line if direction has changed by about 90 degrees.
-                float dot = currentDX * nextDX + currentDY * nextDY;
-                if (dot < ANGLE_THRESHOLD) {
-                    path.add(newDelimiter);
-                    lastDelimiter = newDelimiter;
-                    dX = 0;
-                    dY = 0;
-                    count = 0;
-                }
-            }
-
-            // Vector from last delimiter to most recent point
-            float currentDX = next.x - lastDelimiter.x;
-            float currentDY = next.y - lastDelimiter.y;
-            length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
-
-            // Increment sum of unit vectors from delimiter to each following point
-            count = count + 1;
-            dX = dX + currentDX / length;
-            dY = dY + currentDY / length;
-        }
-
-        path.add(next);
-        Slog.i(LOG_TAG, "path=" + path.toString());
-
-        // Classify line segments, and call Listener callbacks.
-        return recognizeGesturePath(event, policyFlags, path);
-    }
-
-    /**
-     * Classifies a pair of line segments, by direction.
-     * Calls Listener callbacks for success or failure.
-     *
-     * @param event The raw motion event to pass to the listener's onGestureCanceled method.
-     * @param policyFlags Policy flags for the event.
-     * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
-     *
-     * @return true if the event is consumed, else false
-     */
-    private boolean recognizeGesturePath(MotionEvent event, int policyFlags,
-            ArrayList<PointF> path) {
-
-        final int displayId = event.getDisplayId();
-        if (path.size() == 2) {
-            PointF start = path.get(0);
-            PointF end = path.get(1);
-
-            float dX = end.x - start.x;
-            float dY = end.y - start.y;
-            int direction = toDirection(dX, dY);
-            switch (direction) {
-                case LEFT:
-                    return mListener.onGestureCompleted(
-                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_LEFT,
-                                    displayId));
-                case RIGHT:
-                    return mListener.onGestureCompleted(
-                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_RIGHT,
-                                    displayId));
-                case UP:
-                    return mListener.onGestureCompleted(
-                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_UP,
-                                    displayId));
-                case DOWN:
-                    return mListener.onGestureCompleted(
-                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_DOWN,
-                                    displayId));
-                default:
-                    // Do nothing.
-            }
-
-        } else if (path.size() == 3) {
-            PointF start = path.get(0);
-            PointF mid = path.get(1);
-            PointF end = path.get(2);
-
-            float dX0 = mid.x - start.x;
-            float dY0 = mid.y - start.y;
-
-            float dX1 = end.x - mid.x;
-            float dY1 = end.y - mid.y;
-
-            int segmentDirection0 = toDirection(dX0, dY0);
-            int segmentDirection1 = toDirection(dX1, dY1);
-            int gestureId = DIRECTIONS_TO_GESTURE_ID[segmentDirection0][segmentDirection1];
-            return mListener.onGestureCompleted(
-                    new AccessibilityGestureEvent(gestureId, displayId));
-        }
-        // else if (path.size() < 2 || 3 < path.size()) then no gesture recognized.
-        return mListener.onGestureCancelled(event, policyFlags);
-    }
-
-    /** Maps a vector to a dominant direction in set {LEFT, RIGHT, UP, DOWN}. */
-    private static int toDirection(float dX, float dY) {
-        if (Math.abs(dX) > Math.abs(dY)) {
-            // Horizontal
-            return (dX < 0) ? LEFT : RIGHT;
-        } else {
-            // Vertical
-            return (dY < 0) ? UP : DOWN;
-        }
-    }
-
-    private MotionEvent mapSecondPointerToFirstPointer(MotionEvent event) {
-        // Only map basic events when two fingers are down.
-        if (event.getPointerCount() != 2 ||
-                (event.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN &&
-                 event.getActionMasked() != MotionEvent.ACTION_POINTER_UP &&
-                 event.getActionMasked() != MotionEvent.ACTION_MOVE)) {
-            return null;
-        }
-
-        int action = event.getActionMasked();
-
-        if (action == MotionEvent.ACTION_POINTER_DOWN) {
-            action = MotionEvent.ACTION_DOWN;
-        } else if (action == MotionEvent.ACTION_POINTER_UP) {
-            action = MotionEvent.ACTION_UP;
-        }
-
-        // Map the information from the second pointer to the first.
-        return MotionEvent.obtain(mSecondPointerDownTime, event.getEventTime(), action,
-                event.getX(1), event.getY(1), event.getPressure(1), event.getSize(1),
-                event.getMetaState(), event.getXPrecision(), event.getYPrecision(),
-                event.getDeviceId(), event.getEdgeFlags());
-    }
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
new file mode 100644
index 0000000..9b7adc8
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+
+import static com.android.server.accessibility.gestures.Swipe.DOWN;
+import static com.android.server.accessibility.gestures.Swipe.LEFT;
+import static com.android.server.accessibility.gestures.Swipe.RIGHT;
+import static com.android.server.accessibility.gestures.Swipe.UP;
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class coordinates a series of individual gesture matchers to serve as a unified gesture
+ * detector. Gesture matchers are tied to a single gesture. It calls listener callback functions
+ * when a gesture starts or completes.
+ */
+class GestureManifold implements GestureMatcher.StateChangeListener {
+
+    private static final String LOG_TAG = "GestureManifold";
+
+    private final List<GestureMatcher> mGestures = new ArrayList<>();
+    private final Context mContext;
+    // Handler for performing asynchronous operations.
+    private final Handler mHandler;
+    // Listener to be notified of gesture start and end.
+    private Listener mListener;
+    // Shared state information.
+    private TouchState mState;
+
+    GestureManifold(Context context, Listener listener, TouchState state) {
+        mContext = context;
+        mHandler = new Handler(context.getMainLooper());
+        mListener = listener;
+        mState = state;
+        // Set up gestures.
+        // Start with double tap.
+        mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this));
+        mGestures.add(new MultiTapAndHold(context, 2, GESTURE_DOUBLE_TAP_AND_HOLD, this));
+        // One-direction swipes.
+        mGestures.add(new Swipe(context, RIGHT, GESTURE_SWIPE_RIGHT, this));
+        mGestures.add(new Swipe(context, LEFT, GESTURE_SWIPE_LEFT, this));
+        mGestures.add(new Swipe(context, UP, GESTURE_SWIPE_UP, this));
+        mGestures.add(new Swipe(context, DOWN, GESTURE_SWIPE_DOWN, this));
+        // Two-direction swipes.
+        mGestures.add(new Swipe(context, LEFT, RIGHT, GESTURE_SWIPE_LEFT_AND_RIGHT, this));
+        mGestures.add(new Swipe(context, LEFT, UP, GESTURE_SWIPE_LEFT_AND_UP, this));
+        mGestures.add(new Swipe(context, LEFT, DOWN, GESTURE_SWIPE_LEFT_AND_DOWN, this));
+        mGestures.add(new Swipe(context, RIGHT, UP, GESTURE_SWIPE_RIGHT_AND_UP, this));
+        mGestures.add(new Swipe(context, RIGHT, DOWN, GESTURE_SWIPE_RIGHT_AND_DOWN, this));
+        mGestures.add(new Swipe(context, RIGHT, LEFT, GESTURE_SWIPE_RIGHT_AND_LEFT, this));
+        mGestures.add(new Swipe(context, DOWN, UP, GESTURE_SWIPE_DOWN_AND_UP, this));
+        mGestures.add(new Swipe(context, DOWN, LEFT, GESTURE_SWIPE_DOWN_AND_LEFT, this));
+        mGestures.add(new Swipe(context, DOWN, RIGHT, GESTURE_SWIPE_DOWN_AND_RIGHT, this));
+        mGestures.add(new Swipe(context, UP, DOWN, GESTURE_SWIPE_UP_AND_DOWN, this));
+        mGestures.add(new Swipe(context, UP, LEFT, GESTURE_SWIPE_UP_AND_LEFT, this));
+        mGestures.add(new Swipe(context, UP, RIGHT, GESTURE_SWIPE_UP_AND_RIGHT, this));
+    }
+
+    /**
+     * Processes a motion event.
+     *
+     * @param event The event as received from the previous entry in the event stream.
+     * @param rawEvent The event without any transformations e.g. magnification.
+     * @param policyFlags
+     * @return True if the event has been appropriately handled by the gesture manifold and related
+     *     callback functions, false if it should be handled further by the calling function.
+     */
+    boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mState.isClear()) {
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                // Sanity safeguard: if touch state is clear, then matchers should always be clear
+                // before processing the next down event.
+                clear();
+            } else {
+                // If for some reason other events come through while in the clear state they could
+                // compromise the state of particular matchers, so we just ignore them.
+                return false;
+            }
+        }
+        for (GestureMatcher matcher : mGestures) {
+            if (matcher.getState() != GestureMatcher.STATE_GESTURE_CANCELED) {
+                if (DEBUG) {
+                    Slog.d(LOG_TAG, matcher.toString());
+                }
+                matcher.onMotionEvent(event, rawEvent, policyFlags);
+                if (DEBUG) {
+                    Slog.d(LOG_TAG, matcher.toString());
+                }
+                if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) {
+                    // Here we just clear and return. The actual gesture dispatch is done in
+                    // onStateChanged().
+                    clear();
+                    // No need to process this event any further.
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public void clear() {
+        for (GestureMatcher matcher : mGestures) {
+            matcher.clear();
+        }
+    }
+
+    /**
+     * Listener that receives notifications of the state of the gesture detector. Listener functions
+     * are called as a result of onMotionEvent(). The current MotionEvent in the context of these
+     * functions is the event passed into onMotionEvent.
+     */
+    public interface Listener {
+        /**
+         * Called when the user has performed a double tap and then held down the second tap.
+         */
+        void onDoubleTapAndHold();
+
+        /**
+         * Called when the user lifts their finger on the second tap of a double tap.
+         * @return true if the event is consumed, else false
+         */
+        boolean onDoubleTap();
+
+        /**
+         * Called when the system has decided the event stream is a gesture.
+         *
+         * @return true if the event is consumed, else false
+         */
+        boolean onGestureStarted();
+
+        /**
+         * Called when an event stream is recognized as a gesture.
+         *
+         * @param gestureEvent Information about the gesture.
+         * @return true if the event is consumed, else false
+         */
+        boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
+
+        /**
+         * Called when the system has decided an event stream doesn't match any known gesture.
+         *
+         * @param event The most recent MotionEvent received.
+         * @param policyFlags The policy flags of the most recent event.
+         * @return true if the event is consumed, else false
+         */
+        boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+    }
+
+    @Override
+    public void onStateChanged(
+            int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (state == GestureMatcher.STATE_GESTURE_STARTED && !mState.isGestureDetecting()) {
+            mListener.onGestureStarted();
+        } else if (state == GestureMatcher.STATE_GESTURE_COMPLETED) {
+            onGestureCompleted(gestureId);
+        } else if (state == GestureMatcher.STATE_GESTURE_CANCELED && mState.isGestureDetecting()) {
+            // We only want to call the cancelation callback if there are no other pending
+            // detectors.
+            for (GestureMatcher matcher : mGestures) {
+                if (matcher.getState() == GestureMatcher.STATE_GESTURE_STARTED) {
+                    return;
+                }
+            }
+            if (DEBUG) {
+                Slog.d(LOG_TAG, "Cancelling.");
+            }
+            mListener.onGestureCancelled(event, rawEvent, policyFlags);
+        }
+    }
+
+    private void onGestureCompleted(int gestureId) {
+        MotionEvent event = mState.getLastReceivedEvent();
+        // Note that gestures that complete immediately call clear() from onMotionEvent.
+        // Gestures that complete on a delay call clear() here.
+        switch (gestureId) {
+            case GESTURE_DOUBLE_TAP:
+                mListener.onDoubleTap();
+                clear();
+                break;
+            case GESTURE_DOUBLE_TAP_AND_HOLD:
+                mListener.onDoubleTapAndHold();
+                clear();
+                break;
+            default:
+                AccessibilityGestureEvent gestureEvent =
+                        new AccessibilityGestureEvent(gestureId, event.getDisplayId());
+                mListener.onGestureCompleted(gestureEvent);
+                break;
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
new file mode 100644
index 0000000..0b30ff57
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class describes a common base for gesture matchers. A gesture matcher checks a series of
+ * motion events against a single gesture. Coordinating the individual gesture matchers is done by
+ * the GestureManifold. To create a new Gesture, extend this class and override the onDown, onMove,
+ * onUp, etc methods as necessary. If you don't override a method your matcher will do nothing in
+ * response to that type of event. Finally, be sure to give your gesture a name by overriding
+ * getGestureName().
+ */
+abstract class GestureMatcher {
+    // Potential states for this individual gesture matcher.
+    // In STATE_CLEAR, this matcher is accepting new motion events but has not formally signaled
+    // that there is enough data to judge that a gesture has started.
+    static final int STATE_CLEAR = 0;
+    // In STATE_GESTURE_STARTED, this matcher continues to accept motion events and it has signaled
+    // to the gesture manifold that what looks like the specified gesture has started.
+    static final int STATE_GESTURE_STARTED = 1;
+    // In STATE_GESTURE_COMPLETED, this matcher has successfully matched the specified gesture. and
+    // will not accept motion events until it is cleared.
+    static final int STATE_GESTURE_COMPLETED = 2;
+    // In STATE_GESTURE_CANCELED, this matcher will not accept new motion events because it is
+    // impossible that this set of motion events will match the specified gesture.
+    static final int STATE_GESTURE_CANCELED = 3;
+
+    @IntDef({STATE_CLEAR, STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELED})
+    public @interface State {}
+
+    @State private int mState = STATE_CLEAR;
+    // The id number of the gesture that gets passed to accessibility services.
+    private final int mGestureId;
+    // handler for asynchronous operations like timeouts
+    private final Handler mHandler;
+
+    private final StateChangeListener mListener;
+
+    // Use this to transition to new states after a delay.
+    // e.g. cancel or complete after some timeout.
+    // Convenience functions for tapTimeout and doubleTapTimeout are already defined here.
+    protected final DelayedTransition mDelayedTransition;
+
+    GestureMatcher(int gestureId, Handler handler, StateChangeListener listener) {
+        mGestureId = gestureId;
+        mHandler = handler;
+        mDelayedTransition = new DelayedTransition();
+        mListener = listener;
+    }
+
+    /**
+     * Resets all state information for this matcher. Subclasses that include their own state
+     * information should override this method to reset their own state information and call
+     * super.clear().
+     */
+    protected void clear() {
+        mState = STATE_CLEAR;
+        cancelPendingTransitions();
+    }
+
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Transitions to a new state and notifies any listeners. Note that any pending transitions are
+     * canceled.
+     */
+    private void setState(
+            @State int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        mState = state;
+        cancelPendingTransitions();
+        mListener.onStateChanged(mGestureId, mState, event, rawEvent, policyFlags);
+    }
+
+    /** Indicates that there is evidence to suggest that this gesture has started. */
+    protected final void startGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        setState(STATE_GESTURE_STARTED, event, rawEvent, policyFlags);
+    }
+
+    /** Indicates this stream of motion events can no longer match this gesture. */
+    protected final void cancelGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+    }
+
+    /** Indicates this gesture is completed. */
+    protected final void completeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        setState(STATE_GESTURE_COMPLETED, event, rawEvent, policyFlags);
+    }
+
+    public int getGestureId() {
+        return mGestureId;
+    }
+
+    /**
+     * Process a motion event and attempt to match it to this gesture.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     * @return the state of this matcher.
+     */
+    public final int onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mState == STATE_GESTURE_CANCELED || mState == STATE_GESTURE_COMPLETED) {
+            return mState;
+        }
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                onDown(event, rawEvent, policyFlags);
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                onPointerDown(event, rawEvent, policyFlags);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                onMove(event, rawEvent, policyFlags);
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                onPointerUp(event, rawEvent, policyFlags);
+                break;
+            case MotionEvent.ACTION_UP:
+                onUp(event, rawEvent, policyFlags);
+                break;
+            default:
+                // Cancel because of invalid event.
+                setState(STATE_GESTURE_CANCELED, event, rawEvent, policyFlags);
+                break;
+        }
+        return mState;
+    }
+
+    /**
+     * Matchers override this method to respond to ACTION_DOWN events. ACTION_DOWN events indicate
+     * the first finger has touched the screen. If not overridden the default response is to do
+     * nothing.
+     */
+    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /**
+     * Matchers override this method to respond to ACTION_POINTER_DOWN events. ACTION_POINTER_DOWN
+     * indicates that more than one finger has touched the screen. If not overridden the default
+     * response is to do nothing.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     */
+    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /**
+     * Matchers override this method to respond to ACTION_MOVE events. ACTION_MOVE indicates that
+     * one or fingers has moved. If not overridden the default response is to do nothing.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     */
+    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /**
+     * Matchers override this method to respond to ACTION_POINTER_UP events. ACTION_POINTER_UP
+     * indicates that a finger has lifted from the screen but at least one finger continues to touch
+     * the screen. If not overridden the default response is to do nothing.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     */
+    protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /**
+     * Matchers override this method to respond to ACTION_UP events. ACTION_UP indicates that there
+     * are no more fingers touching the screen. If not overridden the default response is to do
+     * nothing.
+     *
+     * @param event the event as passed in from the event stream.
+     * @param rawEvent the original un-modified event. Useful for calculating movements in physical
+     *     space.
+     * @param policyFlags the policy flags as passed in from the event stream.
+     */
+    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {}
+
+    /** Cancels this matcher after the tap timeout. Any pending state transitions are removed. */
+    protected void cancelAfterTapTimeout(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+    }
+
+    /** Cancels this matcher after the double tap timeout. Any pending cancelations are removed. */
+    protected final void cancelAfterDoubleTapTimeout(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+    }
+
+    /**
+     * Cancels this matcher after the specified timeout. Any pending cancelations are removed. Used
+     * to prevent this matcher from accepting motion events until it is cleared.
+     */
+    protected final void cancelAfter(
+            long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        mDelayedTransition.cancel();
+        mDelayedTransition.post(STATE_GESTURE_CANCELED, timeout, event, rawEvent, policyFlags);
+    }
+
+    /** Cancels any delayed transitions between states scheduled for this matcher. */
+    protected final void cancelPendingTransitions() {
+        mDelayedTransition.cancel();
+    }
+
+    /**
+     * Signals that this gesture has been completed after the tap timeout has expired. Used to
+     * ensure that there is no conflict with another gesture or for gestures that explicitly require
+     * a hold.
+     */
+    protected final void completeAfterLongPressTimeout(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        completeAfter(ViewConfiguration.getLongPressTimeout(), event, rawEvent, policyFlags);
+    }
+
+    /**
+     * Signals that this gesture has been completed after the tap timeout has expired. Used to
+     * ensure that there is no conflict with another gesture or for gestures that explicitly require
+     * a hold.
+     */
+    protected final void completeAfterTapTimeout(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        completeAfter(ViewConfiguration.getTapTimeout(), event, rawEvent, policyFlags);
+    }
+
+    /**
+     * Signals that this gesture has been completed after the specified timeout has expired. Used to
+     * ensure that there is no conflict with another gesture or for gestures that explicitly require
+     * a hold.
+     */
+    protected final void completeAfter(
+            long timeout, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        mDelayedTransition.cancel();
+        mDelayedTransition.post(STATE_GESTURE_COMPLETED, timeout, event, rawEvent, policyFlags);
+    }
+
+    /**
+     * Signals that this gesture has been completed after the double-tap timeout has expired. Used
+     * to ensure that there is no conflict with another gesture or for gestures that explicitly
+     * require a hold.
+     */
+    protected final void completeAfterDoubleTapTimeout(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        completeAfter(ViewConfiguration.getDoubleTapTimeout(), event, rawEvent, policyFlags);
+    }
+
+    public static String getStateSymbolicName(@State int state) {
+        switch (state) {
+            case STATE_CLEAR:
+                return "STATE_CLEAR";
+            case STATE_GESTURE_STARTED:
+                return "STATE_GESTURE_STARTED";
+            case STATE_GESTURE_COMPLETED:
+                return "STATE_GESTURE_COMPLETED";
+            case STATE_GESTURE_CANCELED:
+                return "STATE_GESTURE_CANCELED";
+            default:
+                return "Unknown state: " + state;
+        }
+    }
+
+    /**
+     * Returns a readable name for this matcher that can be displayed to the user and in system
+     * logs.
+     */
+    abstract String getGestureName();
+
+    /**
+     * Returns a String representation of this matcher. Each matcher can override this method to add
+     * extra state information to the string representation.
+     */
+    public String toString() {
+        return getGestureName() + ":" + getStateSymbolicName(mState);
+    }
+
+    /** This class allows matchers to transition between states on a delay. */
+    protected final class DelayedTransition implements Runnable {
+
+        private static final String LOG_TAG = "GestureMatcher.DelayedTransition";
+        int mTargetState;
+        MotionEvent mEvent;
+        MotionEvent mRawEvent;
+        int mPolicyFlags;
+
+        public void cancel() {
+            // Avoid meaningless debug messages.
+            if (DEBUG && isPending()) {
+                Slog.d(
+                        LOG_TAG,
+                        getGestureName()
+                                + ": canceling delayed transition to "
+                                + getStateSymbolicName(mTargetState));
+            }
+            mHandler.removeCallbacks(this);
+        }
+
+        public void post(
+                int state, long delay, MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            mTargetState = state;
+            mEvent = event;
+            mRawEvent = rawEvent;
+            mPolicyFlags = policyFlags;
+            mHandler.postDelayed(this, delay);
+            if (DEBUG) {
+                Slog.d(
+                        LOG_TAG,
+                        getGestureName()
+                                + ": posting delayed transition to "
+                                + getStateSymbolicName(mTargetState));
+            }
+        }
+
+        public boolean isPending() {
+            return mHandler.hasCallbacks(this);
+        }
+
+        public void forceSendAndRemove() {
+            if (isPending()) {
+                run();
+                cancel();
+            }
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG) {
+                Slog.d(
+                        LOG_TAG,
+                        getGestureName()
+                                + ": executing delayed transition to "
+                                + getStateSymbolicName(mTargetState));
+            }
+            setState(mTargetState, mEvent, mRawEvent, mPolicyFlags);
+        }
+    }
+
+    /** Interface to allow a class to listen for state changes in a specific gesture matcher */
+    interface StateChangeListener {
+
+        void onStateChanged(
+                int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags);
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
new file mode 100644
index 0000000..2891c6c
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * This class matches multi-tap gestures. The number of taps for each instance is specified in the
+ * constructor.
+ */
+class MultiTap extends GestureMatcher {
+
+    // Maximum reasonable number of taps.
+    public static final int MAX_TAPS = 10;
+    final int mTargetTaps;
+    // The acceptable distance between two taps
+    int mDoubleTapSlop;
+    // The acceptable distance the pointer can move and still count as a tap.
+    int mTouchSlop;
+    int mTapTimeout;
+    int mDoubleTapTimeout;
+    int mCurrentTaps;
+    float mBaseX;
+    float mBaseY;
+
+    MultiTap(Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+        super(gesture, new Handler(context.getMainLooper()), listener);
+        mTargetTaps = taps;
+        mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mTapTimeout = ViewConfiguration.getTapTimeout();
+        mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+        clear();
+    }
+
+    @Override
+    protected void clear() {
+        mCurrentTaps = 0;
+        mBaseX = Float.NaN;
+        mBaseY = Float.NaN;
+        super.clear();
+    }
+
+    @Override
+    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfterTapTimeout(event, rawEvent, policyFlags);
+        if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+            mBaseX = event.getX();
+            mBaseY = event.getY();
+        }
+        if (!isInsideSlop(rawEvent, mDoubleTapSlop)) {
+            cancelGesture(event, rawEvent, policyFlags);
+        }
+        mBaseX = event.getX();
+        mBaseY = event.getY();
+    }
+
+    @Override
+    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+        if (!isInsideSlop(rawEvent, mTouchSlop)) {
+            cancelGesture(event, rawEvent, policyFlags);
+        }
+        if (getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) {
+            mCurrentTaps++;
+            if (mCurrentTaps == mTargetTaps) {
+                // Done.
+                completeAfterTapTimeout(event, rawEvent, policyFlags);
+                return;
+            }
+            // Needs more taps.
+            cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags);
+        } else {
+            // Either too many taps or nonsensical event stream.
+            cancelGesture(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (!isInsideSlop(rawEvent, mTouchSlop)) {
+            cancelGesture(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelGesture(event, rawEvent, policyFlags);
+    }
+
+    @Override
+    protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelGesture(event, rawEvent, policyFlags);
+    }
+
+    @Override
+    public String getGestureName() {
+        switch (mTargetTaps) {
+            case 2:
+                return "Double Tap";
+            case 3:
+                return "Triple Tap";
+            default:
+                return Integer.toString(mTargetTaps) + " Taps";
+        }
+    }
+
+    private boolean isInsideSlop(MotionEvent rawEvent, int slop) {
+        final float deltaX = mBaseX - rawEvent.getX();
+        final float deltaY = mBaseY - rawEvent.getY();
+        if (deltaX == 0 && deltaY == 0) {
+            return true;
+        }
+        final double moveDelta = Math.hypot(deltaX, deltaY);
+        return moveDelta <= slop;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString()
+                + ", Taps:"
+                + mCurrentTaps
+                + ", mBaseX: "
+                + Float.toString(mBaseX)
+                + ", mBaseY: "
+                + Float.toString(mBaseY);
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
new file mode 100644
index 0000000..6a1f1a5
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTapAndHold.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import android.content.Context;
+import android.view.MotionEvent;
+
+/**
+ * This class matches gestures of the form multi-tap and hold. The number of taps for each instance
+ * is specified in the constructor.
+ */
+class MultiTapAndHold extends MultiTap {
+    MultiTapAndHold(
+            Context context, int taps, int gesture, GestureMatcher.StateChangeListener listener) {
+        super(context, taps, gesture, listener);
+    }
+
+    @Override
+    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        super.onDown(event, rawEvent, policyFlags);
+        if (mCurrentTaps + 1 == mTargetTaps) {
+            completeAfterLongPressTimeout(event, rawEvent, policyFlags);
+        }
+    }
+
+    @Override
+    public String getGestureName() {
+        switch (mTargetTaps) {
+            case 2:
+                return "Double Tap and Hold";
+            case 3:
+                return "Triple Tap and Hold";
+            default:
+                return Integer.toString(mTargetTaps) + " Taps and Hold";
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
new file mode 100644
index 0000000..b246c67
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/Swipe.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+
+import android.content.Context;
+import android.gesture.GesturePoint;
+import android.graphics.PointF;
+import android.os.Handler;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+
+import java.util.ArrayList;
+
+/**
+ * This class is responsible for matching one-finger swipe gestures. Each instance matches one swipe
+ * gesture. A swipe is specified as a series of one or more directions e.g. left, left and up, etc.
+ * At this time swipes with more than two directions are not supported.
+ */
+class Swipe extends GestureMatcher {
+
+    // Direction constants.
+    public static final int LEFT = 0;
+    public static final int RIGHT = 1;
+    public static final int UP = 2;
+    public static final int DOWN = 3;
+    // This is the calculated movement threshold used track if the user is still
+    // moving their finger.
+    private final float mGestureDetectionThreshold;
+
+    // Buffer for storing points for gesture detection.
+    private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+    // The minimal delta between moves to add a gesture point.
+    private static final int TOUCH_TOLERANCE_PIX = 3;
+
+    // The minimal score for accepting a predicted gesture.
+    private static final float MIN_PREDICTION_SCORE = 2.0f;
+
+    // Distance a finger must travel before we decide if it is a gesture or not.
+    private static final int GESTURE_CONFIRM_CM = 1;
+
+    // Time threshold used to determine if an interaction is a gesture or not.
+    // If the first movement of 1cm takes longer than this value, we assume it's
+    // a slow movement, and therefore not a gesture.
+    //
+    // This value was determined by measuring the time for the first 1cm
+    // movement when gesturing, and touch exploring.  Based on user testing,
+    // all gestures started with the initial movement taking less than 100ms.
+    // When touch exploring, the first movement almost always takes longer than
+    // 200ms.
+    private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
+
+    // Time threshold used to determine if a gesture should be cancelled.  If
+    // the finger takes more than this time to move 1cm, the ongoing gesture is
+    // cancelled.
+    private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
+
+    private int[] mDirections;
+    private float mBaseX;
+    private float mBaseY;
+    private long mBaseTime;
+    private float mPreviousGestureX;
+    private float mPreviousGestureY;
+    // Constants for sampling motion event points.
+    // We sample based on a minimum distance between points, primarily to improve accuracy by
+    // reducing noisy minor changes in direction.
+    private static final float MIN_CM_BETWEEN_SAMPLES = 0.25f;
+    private final float mMinPixelsBetweenSamplesX;
+    private final float mMinPixelsBetweenSamplesY;
+
+    // Constants for separating gesture segments
+    private static final float ANGLE_THRESHOLD = 0.0f;
+
+    Swipe(
+            Context context,
+            int direction,
+            int gesture,
+            GestureMatcher.StateChangeListener listener) {
+        this(context, new int[] {direction}, gesture, listener);
+    }
+
+    Swipe(
+            Context context,
+            int direction1,
+            int direction2,
+            int gesture,
+            GestureMatcher.StateChangeListener listener) {
+        this(context, new int[] {direction1, direction2}, gesture, listener);
+    }
+
+    private Swipe(
+            Context context,
+            int[] directions,
+            int gesture,
+            GestureMatcher.StateChangeListener listener) {
+        super(gesture, new Handler(context.getMainLooper()), listener);
+        mDirections = directions;
+        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+        mGestureDetectionThreshold =
+                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10, displayMetrics)
+                        * GESTURE_CONFIRM_CM;
+        // Calculate minimum gesture velocity
+        final float pixelsPerCmX = displayMetrics.xdpi / 2.54f;
+        final float pixelsPerCmY = displayMetrics.ydpi / 2.54f;
+        mMinPixelsBetweenSamplesX = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmX;
+        mMinPixelsBetweenSamplesY = MIN_CM_BETWEEN_SAMPLES * pixelsPerCmY;
+        clear();
+    }
+
+    @Override
+    protected void clear() {
+        mBaseX = Float.NaN;
+        mBaseY = Float.NaN;
+        mBaseTime = 0;
+        mStrokeBuffer.clear();
+        super.clear();
+    }
+
+    @Override
+    protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelAfterDelay(event, rawEvent, policyFlags);
+        if (Float.isNaN(mBaseX) && Float.isNaN(mBaseY)) {
+            mBaseX = rawEvent.getX();
+            mBaseY = rawEvent.getY();
+            mBaseTime = event.getEventTime();
+            mPreviousGestureX = mBaseX;
+            mPreviousGestureY = mBaseY;
+        }
+        // Otherwise do nothing because this event doesn't make sense in the middle of a gesture.
+    }
+
+    @Override
+    protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        final float x = rawEvent.getX();
+        final float y = rawEvent.getY();
+        final long time = event.getEventTime();
+        final float dX = Math.abs(x - mPreviousGestureX);
+        final float dY = Math.abs(y - mPreviousGestureY);
+        final long timeDelta = time - mBaseTime;
+        final double moveDelta = Math.hypot(Math.abs(x - mBaseX), Math.abs(y - mBaseY));
+        if (DEBUG) {
+            Slog.d(
+                    getGestureName(),
+                    "moveDelta:"
+                            + Double.toString(moveDelta)
+                            + " mGestureDetectionThreshold: "
+                            + Float.toString(mGestureDetectionThreshold));
+        }
+        if (getState() == STATE_CLEAR) {
+            if (mStrokeBuffer.size() == 0) {
+                // First, make sure the pointer is going in the right direction.
+                cancelAfterDelay(event, rawEvent, policyFlags);
+                int direction = toDirection(x - mBaseX, y - mBaseY);
+                if (direction != mDirections[0]) {
+                    cancelGesture(event, rawEvent, policyFlags);
+                    return;
+                } else {
+                    // This is confirmed to be some kind of swipe so start tracking points.
+                    mStrokeBuffer.add(new GesturePoint(mBaseX, mBaseY, mBaseTime));
+                }
+            }
+            if (moveDelta > mGestureDetectionThreshold) {
+                // If the pointer has moved more than the threshold,
+                // update the stored values.
+                mBaseX = x;
+                mBaseY = y;
+                mBaseTime = time;
+                if (getState() == STATE_CLEAR) {
+                    startGesture(event, rawEvent, policyFlags);
+                    cancelAfterDelay(event, rawEvent, policyFlags);
+                }
+            }
+        }
+        if (getState() == STATE_GESTURE_STARTED) {
+            if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+                mPreviousGestureX = x;
+                mPreviousGestureY = y;
+                mStrokeBuffer.add(new GesturePoint(x, y, time));
+                cancelAfterDelay(event, rawEvent, policyFlags);
+            }
+        }
+    }
+
+    @Override
+    protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (getState() != STATE_GESTURE_STARTED) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+
+        final float x = rawEvent.getX();
+        final float y = rawEvent.getY();
+        final long time = event.getEventTime();
+        final float dX = Math.abs(x - mPreviousGestureX);
+        final float dY = Math.abs(y - mPreviousGestureY);
+        if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+            mStrokeBuffer.add(new GesturePoint(x, y, time));
+        }
+        recognizeGesture(event, rawEvent, policyFlags);
+    }
+
+    @Override
+    protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelGesture(event, rawEvent, policyFlags);
+    }
+
+    @Override
+    protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelGesture(event, rawEvent, policyFlags);
+    }
+
+    /**
+     * queues a transition to STATE_GESTURE_CANCEL based on the current state. If we have
+     * transitioned to STATE_GESTURE_STARTED the delay is longer.
+     */
+    private void cancelAfterDelay(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        cancelPendingTransitions();
+        switch (getState()) {
+            case STATE_CLEAR:
+                cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS, event, rawEvent, policyFlags);
+                break;
+            case STATE_GESTURE_STARTED:
+                cancelAfter(CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS, event, rawEvent, policyFlags);
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
+     * Listener callbacks for success or failure.
+     *
+     * @param event The raw motion event to pass to the listener callbacks.
+     * @param policyFlags Policy flags for the event.
+     * @return true if the event is consumed, else false
+     */
+    private void recognizeGesture(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (mStrokeBuffer.size() < 2) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+
+        // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
+        // direction change.
+        // Method: for each sampled motion event, check the angle of the most recent motion vector
+        // versus the preceding motion vector, and segment the line if the angle is about
+        // 90 degrees.
+
+        ArrayList<PointF> path = new ArrayList<>();
+        PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
+        path.add(lastDelimiter);
+
+        float dX = 0; // Sum of unit vectors from last delimiter to each following point
+        float dY = 0;
+        int count = 0; // Number of points since last delimiter
+        float length = 0; // Vector length from delimiter to most recent point
+
+        PointF next = new PointF();
+        for (int i = 1; i < mStrokeBuffer.size(); ++i) {
+            next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
+            if (count > 0) {
+                // Average of unit vectors from delimiter to following points
+                float currentDX = dX / count;
+                float currentDY = dY / count;
+
+                // newDelimiter is a possible new delimiter, based on a vector with length from
+                // the last delimiter to the previous point, but in the direction of the average
+                // unit vector from delimiter to previous points.
+                // Using the averaged vector has the effect of "squaring off the curve",
+                // creating a sharper angle between the last motion and the preceding motion from
+                // the delimiter. In turn, this sharper angle achieves the splitting threshold
+                // even in a gentle curve.
+                PointF newDelimiter =
+                        new PointF(
+                                length * currentDX + lastDelimiter.x,
+                                length * currentDY + lastDelimiter.y);
+
+                // Unit vector from newDelimiter to the most recent point
+                float nextDX = next.x - newDelimiter.x;
+                float nextDY = next.y - newDelimiter.y;
+                float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
+                nextDX = nextDX / nextLength;
+                nextDY = nextDY / nextLength;
+
+                // Compare the initial motion direction to the most recent motion direction,
+                // and segment the line if direction has changed by about 90 degrees.
+                float dot = currentDX * nextDX + currentDY * nextDY;
+                if (dot < ANGLE_THRESHOLD) {
+                    path.add(newDelimiter);
+                    lastDelimiter = newDelimiter;
+                    dX = 0;
+                    dY = 0;
+                    count = 0;
+                }
+            }
+
+            // Vector from last delimiter to most recent point
+            float currentDX = next.x - lastDelimiter.x;
+            float currentDY = next.y - lastDelimiter.y;
+            length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
+
+            // Increment sum of unit vectors from delimiter to each following point
+            count = count + 1;
+            dX = dX + currentDX / length;
+            dY = dY + currentDY / length;
+        }
+
+        path.add(next);
+        if (DEBUG) {
+            Slog.d(getGestureName(), "path=" + path.toString());
+        }
+        // Classify line segments, and call Listener callbacks.
+        recognizeGesturePath(event, rawEvent, policyFlags, path);
+    }
+
+    /**
+     * Classifies a pair of line segments, by direction. Calls Listener callbacks for success or
+     * failure.
+     *
+     * @param event The raw motion event to pass to the listener's onGestureCanceled method.
+     * @param policyFlags Policy flags for the event.
+     * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
+     * @return true if the event is consumed, else false
+     */
+    private void recognizeGesturePath(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags, ArrayList<PointF> path) {
+
+        final int displayId = event.getDisplayId();
+        if (path.size() != mDirections.length + 1) {
+            cancelGesture(event, rawEvent, policyFlags);
+            return;
+        }
+        for (int i = 0; i < path.size() - 1; ++i) {
+            PointF start = path.get(i);
+            PointF end = path.get(i + 1);
+
+            float dX = end.x - start.x;
+            float dY = end.y - start.y;
+            int direction = toDirection(dX, dY);
+            if (direction != mDirections[i]) {
+                if (DEBUG) {
+                    Slog.d(
+                            getGestureName(),
+                            "Found direction "
+                                    + directionToString(direction)
+                                    + " when expecting "
+                                    + directionToString(mDirections[i]));
+                }
+                cancelGesture(event, rawEvent, policyFlags);
+                return;
+            }
+        }
+        if (DEBUG) {
+            Slog.d(getGestureName(), "Completed.");
+        }
+        completeGesture(event, rawEvent, policyFlags);
+    }
+
+    private static int toDirection(float dX, float dY) {
+        if (Math.abs(dX) > Math.abs(dY)) {
+            // Horizontal
+            return (dX < 0) ? LEFT : RIGHT;
+        } else {
+            // Vertical
+            return (dY < 0) ? UP : DOWN;
+        }
+    }
+
+    public static String directionToString(int direction) {
+        switch (direction) {
+            case LEFT:
+                return "left";
+            case RIGHT:
+                return "right";
+            case UP:
+                return "up";
+            case DOWN:
+                return "down";
+            default:
+                return "Unknown Direction";
+        }
+    }
+
+    @Override
+    String getGestureName() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Swipe ").append(directionToString(mDirections[0]));
+        for (int i = 1; i < mDirections.length; ++i) {
+            builder.append(" and ").append(directionToString(mDirections[i]));
+        }
+        return builder.toString();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder(super.toString());
+        if (getState() != STATE_GESTURE_CANCELED) {
+            builder.append(", mBaseX: ")
+                    .append(mBaseX)
+                    .append(", mBaseY: ")
+                    .append(mBaseY)
+                    .append(", mGestureDetectionThreshold:")
+                    .append(mGestureDetectionThreshold)
+                    .append(", mMinPixelsBetweenSamplesX:")
+                    .append(mMinPixelsBetweenSamplesX)
+                    .append(", mMinPixelsBetweenSamplesY:")
+                    .append(mMinPixelsBetweenSamplesY);
+        }
+        return builder.toString();
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index b62e260..5f41638 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -59,7 +59,7 @@
  * @hide
  */
 public class TouchExplorer extends BaseEventStreamTransformation
-        implements AccessibilityGestureDetector.Listener {
+        implements GestureManifold.Listener {
 
     static final boolean DEBUG = false;
 
@@ -104,7 +104,7 @@
     private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;
 
     // Helper to detect gestures.
-    private final AccessibilityGestureDetector mGestureDetector;
+    private final GestureManifold  mGestureDetector;
 
     // Helper class to track received pointers.
     private final TouchState.ReceivedPointerTracker mReceivedPointerTracker;
@@ -142,7 +142,7 @@
      *                one created in place, or for testing purpose.
      */
     public TouchExplorer(Context context, AccessibilityManagerService service,
-            AccessibilityGestureDetector detector) {
+            GestureManifold detector) {
         mContext = context;
         mAms = service;
         mState = new TouchState();
@@ -161,7 +161,7 @@
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
                 mDetermineUserIntentTimeout);
         if (detector == null) {
-            mGestureDetector = new AccessibilityGestureDetector(context, this);
+            mGestureDetector = new GestureManifold(context, this, mState);
         } else {
             mGestureDetector = detector;
         }
@@ -285,7 +285,7 @@
     }
 
     @Override
-    public void onDoubleTapAndHold(MotionEvent event, int policyFlags) {
+    public void onDoubleTapAndHold() {
         // Ignore the event if we aren't touch interacting.
         if (!mState.isTouchInteracting()) {
             return;
@@ -303,7 +303,7 @@
     }
 
     @Override
-    public boolean onDoubleTap(MotionEvent event, int policyFlags) {
+    public boolean onDoubleTap() {
         if (!mState.isTouchInteracting()) {
             return false;
         }
@@ -319,7 +319,7 @@
 
         // Announce the end of a new touch interaction.
         mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
-
+        mSendTouchInteractionEndDelayed.cancel();
         // Try to use the standard accessibility API to click
         if (!mAms.performActionOnAccessibilityFocusedItem(
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
@@ -356,7 +356,7 @@
     }
 
     @Override
-    public boolean onGestureCancelled(MotionEvent event, int policyFlags) {
+    public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (mState.isGestureDetecting()) {
             endGestureDetection(event.getActionMasked() == MotionEvent.ACTION_UP);
             return true;
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index f463260..d23dbbe 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -71,7 +71,10 @@
     // Helper class to track received pointers.
     // Todo: collapse or hide this class so multiple classes don't modify it.
     private final ReceivedPointerTracker mReceivedPointerTracker;
+    // The most recently received motion event.
     private MotionEvent mLastReceivedEvent;
+    // The accompanying raw event without any transformations.
+    private MotionEvent mLastReceivedRawEvent;
 
     public TouchState() {
         mReceivedPointerTracker = new ReceivedPointerTracker();
@@ -97,6 +100,9 @@
         if (mLastReceivedEvent != null) {
             mLastReceivedEvent.recycle();
         }
+        if (mLastReceivedRawEvent != null) {
+            mLastReceivedRawEvent.recycle();
+        }
         mLastReceivedEvent = MotionEvent.obtain(rawEvent);
         mReceivedPointerTracker.onMotionEvent(rawEvent);
     }
@@ -246,7 +252,6 @@
         // or if it goes up the next one that most recently went down.
         private int mPrimaryPointerId;
 
-
         ReceivedPointerTracker() {
             clear();
         }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index d7ed2e9..202f900 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -82,6 +82,7 @@
 import com.android.server.autofill.RemoteAugmentedAutofillService.RemoteAugmentedAutofillServiceCallbacks;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -168,6 +169,8 @@
     @Nullable
     private ServiceInfo mRemoteAugmentedAutofillServiceInfo;
 
+    private final InputMethodManagerInternal mInputMethodManagerInternal;
+
     AutofillManagerServiceImpl(AutofillManagerService master, Object lock,
             LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
             AutofillCompatState autofillCompatState,
@@ -179,6 +182,7 @@
         mUi = ui;
         mFieldClassificationStrategy = new FieldClassificationStrategy(getContext(), userId);
         mAutofillCompatState = autofillCompatState;
+        mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
 
         updateLocked(disabled);
     }
@@ -493,7 +497,7 @@
                 sessionId, taskId, uid, activityToken, appCallbackToken, hasCallback,
                 mUiLatencyHistory, mWtfHistory, serviceComponentName,
                 componentName, compatMode, bindInstantServiceAllowed, forAugmentedAutofillOnly,
-                flags);
+                flags, mInputMethodManagerInternal);
         mSessions.put(newSession.id, newSession);
 
         return newSession;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3b2da91..67bcccd 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -42,6 +42,8 @@
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.AutofillOverlay;
 import android.app.assist.AssistStructure.ViewNode;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -80,6 +82,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LocalLog;
+import android.util.Log;
+import android.util.Size;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -90,22 +94,38 @@
 import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutoFillManagerClient;
 import android.view.autofill.IAutofillWindowPresenter;
+import android.view.inline.InlinePresentationSpec;
+import android.view.inputmethod.InlineSuggestion;
+import android.view.inputmethod.InlineSuggestionInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.internal.view.inline.IInlineContentCallback;
+import com.android.internal.view.inline.IInlineContentProvider;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -290,6 +310,23 @@
     @GuardedBy("mLock")
     private boolean mForAugmentedAutofillOnly;
 
+    @NonNull
+    private final InputMethodManagerInternal mInputMethodManagerInternal;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private CompletableFuture<IInlineSuggestionsResponseCallback>
+            mInlineSuggestionsResponseCallbackFuture;
+
+    @Nullable
+    private InlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback;
+
+    private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
+
     /**
      * Receiver of assist data from the app's {@link Activity}.
      */
@@ -386,7 +423,23 @@
 
                 final ArrayList<FillContext> contexts =
                         mergePreviousSessionLocked(/* forSave= */ false);
-                request = new FillRequest(requestId, contexts, mClientState, flags);
+
+                InlineSuggestionsRequest suggestionsRequest = null;
+                if (mSuggestionsRequestFuture != null) {
+                    try {
+                        suggestionsRequest = mSuggestionsRequestFuture.get(
+                                INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                    } catch (TimeoutException e) {
+                        Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
+                    } catch (CancellationException e) {
+                        Log.w(TAG, "Inline suggestions request cancelled");
+                    } catch (InterruptedException | ExecutionException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+
+                request = new FillRequest(requestId, contexts, mClientState, flags,
+                        suggestionsRequest);
             }
 
             mRemoteFillService.onFillRequest(request);
@@ -569,6 +622,70 @@
     }
 
     /**
+     * Returns whether inline suggestions are enabled for Autofill.
+     */
+    // TODO(b/137800469): Implement this
+    private boolean isInlineSuggestionsEnabled() {
+        return true;
+    }
+
+    /**
+     * Ask the IME to make an inline suggestions request if enabled.
+     */
+    private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
+            int newState, int flags) {
+        if (isInlineSuggestionsEnabled()) {
+            mSuggestionsRequestFuture = new CompletableFuture<>();
+            mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>();
+
+            if (mInlineSuggestionsRequestCallback == null) {
+                mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallback(this);
+            }
+
+            mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
+                    mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
+        }
+
+        requestNewFillResponseLocked(viewState, newState, flags);
+    }
+
+    private static final class InlineSuggestionsRequestCallback
+            extends IInlineSuggestionsRequestCallback.Stub {
+        private final WeakReference<Session> mSession;
+
+        private InlineSuggestionsRequestCallback(Session session) {
+            mSession = new WeakReference<>(session);
+        }
+
+        @Override
+        public void onInlineSuggestionsUnsupported() throws RemoteException {
+            Log.i(TAG, "inline suggestions request unsupported, "
+                    + "falling back to regular autofill");
+            final Session session = mSession.get();
+            if (session != null) {
+                synchronized (session.mLock) {
+                    session.mSuggestionsRequestFuture.cancel(true);
+                    session.mInlineSuggestionsResponseCallbackFuture.cancel(true);
+                }
+            }
+        }
+
+        @Override
+        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+                IInlineSuggestionsResponseCallback callback) throws RemoteException {
+            Log.i(TAG, "onInlineSuggestionsRequest() received: "
+                    + request);
+            final Session session = mSession.get();
+            if (session != null) {
+                synchronized (session.mLock) {
+                    session.mSuggestionsRequestFuture.complete(request);
+                    session.mInlineSuggestionsResponseCallbackFuture.complete(callback);
+                }
+            }
+        }
+    }
+
+    /**
      * Reads a new structure and then request a new fill response from the fill service.
      */
     @GuardedBy("mLock")
@@ -584,6 +701,7 @@
             triggerAugmentedAutofillLocked();
             return;
         }
+
         viewState.setState(newState);
 
         int requestId;
@@ -636,7 +754,8 @@
             @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
             @NonNull LocalLog wtfHistory, @Nullable ComponentName serviceComponentName,
             @NonNull ComponentName componentName, boolean compatMode,
-            boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags) {
+            boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags,
+            @NonNull InputMethodManagerInternal inputMethodManagerInternal) {
         if (sessionId < 0) {
             wtf(null, "Non-positive sessionId: %s", sessionId);
         }
@@ -661,6 +780,8 @@
         mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
         setClientLocked(client);
 
+        mInputMethodManagerInternal = inputMethodManagerInternal;
+
         mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
     }
@@ -2208,7 +2329,8 @@
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
             mForAugmentedAutofillOnly = false;
             if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
-            requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
+            maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+                    ViewState.STATE_RESTARTED_SESSION, flags);
             return;
         }
 
@@ -2218,7 +2340,8 @@
                 Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
                         + viewState.getStateAsString());
             }
-            requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
+            maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+                    ViewState.STATE_STARTED_PARTITION, flags);
         } else {
             if (sVerbose) {
                 Slog.v(TAG, "Not starting new partition for view " + id + ": "
@@ -2325,7 +2448,8 @@
                 // View is triggering autofill.
                 mCurrentViewId = viewState.id;
                 viewState.update(value, virtualBounds, flags);
-                requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
+                maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
+                        ViewState.STATE_STARTED_SESSION, flags);
                 break;
             case ACTION_VALUE_CHANGED:
                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -2527,6 +2651,16 @@
             wtf(null, "onFillReady(): no service label or icon");
             return;
         }
+
+        final List<Slice> inlineSuggestionSlices = response.getInlineSuggestionSlices();
+        if (inlineSuggestionSlices != null) {
+            if (requestShowInlineSuggestions(inlineSuggestionSlices, response)) {
+                //TODO(b/137800469): Add logging instead of bypassing below logic.
+                return;
+            }
+        }
+
+
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
                 serviceLabel, serviceIcon, this, id, mCompatMode);
@@ -2558,6 +2692,109 @@
         }
     }
 
+    /**
+     * Returns whether we made a request to show inline suggestions.
+     */
+    private boolean requestShowInlineSuggestions(List<Slice> inlineSuggestionSlices,
+            FillResponse response) {
+        IInlineSuggestionsResponseCallback inlineContentCallback = null;
+        synchronized (mLock) {
+            if (mInlineSuggestionsResponseCallbackFuture != null) {
+                try {
+                    inlineContentCallback = mInlineSuggestionsResponseCallbackFuture.get(
+                            INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                } catch (TimeoutException e) {
+                    Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
+                } catch (CancellationException e) {
+                    Log.w(TAG, "Inline suggestions callback cancelled");
+                } catch (InterruptedException | ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        if (inlineContentCallback == null) {
+            Log.w(TAG, "Session input method callback is not set yet");
+            return false;
+        }
+
+        final List<Dataset> datasets = response.getDatasets();
+        if (datasets == null) {
+            Log.w(TAG, "response returned null datasets");
+            return false;
+        }
+
+        final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
+        final int slicesSize = inlineSuggestionSlices.size();
+        if (datasets.size() < slicesSize) {
+            Log.w(TAG, "Too many slices provided, not enough corresponding datasets");
+            return false;
+        }
+
+        for (int sliceIndex = 0; sliceIndex < slicesSize; sliceIndex++) {
+            Log.i(TAG, "Reading slice-" + sliceIndex + " at requestshowinlinesuggestions");
+            final Slice inlineSuggestionSlice = inlineSuggestionSlices.get(sliceIndex);
+            final List<SliceItem> sliceItems = inlineSuggestionSlice.getItems();
+
+            final int itemsSize = sliceItems.size();
+            int minWidth = -1;
+            int maxWidth = -1;
+            int minHeight = -1;
+            int maxHeight = -1;
+            for (int itemIndex = 0; itemIndex < itemsSize; itemIndex++) {
+                final SliceItem item = sliceItems.get(itemIndex);
+                final String subtype = item.getSubType();
+                switch (item.getSubType()) {
+                    case "SUBTYPE_MIN_WIDTH":
+                        minWidth = item.getInt();
+                        break;
+                    case "SUBTYPE_MAX_WIDTH":
+                        maxWidth = item.getInt();
+                        break;
+                    case "SUBTYPE_MIN_HEIGHT":
+                        minHeight = item.getInt();
+                        break;
+                    case "SUBTYPE_MAX_HEIGHT":
+                        maxHeight = item.getInt();
+                        break;
+                    default:
+                        Log.i(TAG, "unrecognized inline suggestions subtype: " + subtype);
+                }
+            }
+
+            if (minWidth < 0 || maxWidth < 0 || minHeight < 0 || maxHeight < 0) {
+                Log.w(TAG, "missing inline suggestion requirements");
+                return false;
+            }
+
+            final InlinePresentationSpec spec = new InlinePresentationSpec.Builder(
+                    new Size(minWidth, minHeight), new Size(maxWidth, maxHeight)).build();
+            final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
+                    spec, InlineSuggestionInfo.SOURCE_AUTOFILL, new String[] { "" });
+            final Dataset dataset = datasets.get(sliceIndex);
+
+            inlineSuggestions.add(new InlineSuggestion(inlineSuggestionInfo,
+                    new IInlineContentProvider.Stub() {
+                        @Override
+                        public void provideContent(int width, int height,
+                                IInlineContentCallback callback) throws RemoteException {
+                            getUiForShowing().getSuggestionSurfaceForShowing(dataset, response,
+                                    mCurrentViewId, width, height, callback);
+                        }
+                    }));
+        }
+
+        try  {
+            inlineContentCallback.onInlineSuggestionsResponse(
+                    new InlineSuggestionsResponse(inlineSuggestions));
+        } catch (RemoteException e) {
+            Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()");
+            return false;
+        }
+
+        return true;
+    }
+
     boolean isDestroyed() {
         synchronized (mLock) {
             return mDestroyed;
@@ -2831,6 +3068,7 @@
 
         final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
 
+        // TODO(b/137800469): implement inlined suggestions for augmented autofill
         remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
                 currentValue);
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 26bb7c3..eadfd31 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -24,6 +24,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
 import android.metrics.LogMaker;
 import android.os.Bundle;
@@ -37,13 +39,19 @@
 import android.text.TextUtils;
 import android.util.Slog;
 import android.view.KeyEvent;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowlessViewRoot;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
 import android.view.autofill.IAutofillWindowPresenter;
+import android.widget.TextView;
 import android.widget.Toast;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.view.inline.IInlineContentCallback;
 import com.android.server.LocalServices;
 import com.android.server.UiModeManagerInternal;
 import com.android.server.UiThread;
@@ -171,6 +179,75 @@
     }
 
     /**
+     * TODO(b/137800469): Fill in javadoc.
+     * TODO(b/137800469): peoperly manage lifecycle of suggestions surfaces.
+     */
+    public void getSuggestionSurfaceForShowing(@NonNull Dataset dataset,
+            @NonNull FillResponse response, AutofillId autofillId, int width, int height,
+            IInlineContentCallback cb) {
+        if (dataset == null) {
+            Slog.w(TAG, "getSuggestionSurfaceForShowing() called with null dataset");
+        }
+        mHandler.post(() -> {
+            final SurfaceControl suggestionSurface = inflateInlineSuggestion(dataset, response,
+                    autofillId, width, height);
+
+            try {
+                cb.onContent(suggestionSurface);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "RemoteException replying onContent(" + suggestionSurface + "): " + e);
+            }
+        });
+    }
+
+    /**
+     * TODO(b/137800469): Fill in javadoc, generate custom templated view for inline suggestions.
+     * TODO: Move to ExtServices.
+     *
+     * @return a {@link SurfaceControl} with the inflated content embedded in it.
+     */
+    private SurfaceControl inflateInlineSuggestion(@NonNull Dataset dataset,
+            @NonNull FillResponse response, AutofillId autofillId, int width, int height) {
+        Slog.i(TAG, "inflate() called");
+        final Context context = mContext;
+        final int index = dataset.getFieldIds().indexOf(autofillId);
+        if (index < 0) {
+            Slog.w(TAG, "inflateInlineSuggestion(): AutofillId=" + autofillId
+                    + " not found in dataset");
+        }
+
+        final AutofillValue datasetValue = dataset.getFieldValues().get(index);
+        final SurfaceControl sc = new SurfaceControl.Builder()
+                // TODO(b/137800469): sanitize name
+                .setName("af suggestion")
+                .build();
+
+        //TODO(b/137800469): Pass in inputToken from IME.
+        final WindowlessViewRoot wvr = new WindowlessViewRoot(context, context.getDisplay(), sc,
+                null);
+
+        TextView textView = new TextView(context);
+        textView.setText(datasetValue.getTextValue());
+        textView.setBackgroundColor(Color.WHITE);
+        textView.setTextColor(Color.BLACK);
+        textView.setOnClickListener(v -> {
+            Slog.d(TAG, "Inline suggestion clicked");
+            hideFillUiUiThread(mCallback, true);
+            if (mCallback != null) {
+                final int datasetIndex = response.getDatasets().indexOf(dataset);
+                mCallback.fill(response.getRequestId(), datasetIndex, dataset);
+            }
+        });
+
+        WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(width, height,
+                        WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+        wvr.addView(textView, lp);
+
+        return sc;
+    }
+
+    /**
      * Shows the fill UI, removing the previous fill UI if the has changed.
      *
      * @param focusedId the currently focused field
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 203bc61..b7adfa4 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -116,6 +116,7 @@
         "android.hardware.oemlock-V1.0-java",
         "android.hardware.configstore-V1.0-java",
         "android.hardware.contexthub-V1.0-java",
+        "android.hardware.soundtrigger-V2.3-java",
         "android.hidl.manager-V1.2-java",
         "dnsresolver_aidl_interface-V2-java",
         "netd_event_listener_interface-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 53f306b..49046b2 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -275,6 +275,11 @@
     public abstract ComponentName getDefaultHomeActivity(int userId);
 
     /**
+     * @return The SystemUI service component name.
+     */
+    public abstract ComponentName getSystemUiServiceComponent();
+
+    /**
      * Called by DeviceOwnerManagerService to set the package names of device owner and profile
      * owners.
      */
@@ -336,14 +341,16 @@
      * @param responseObj The response of the first phase of ephemeral resolution
      * @param origIntent The original intent that triggered ephemeral resolution
      * @param resolvedType The resolved type of the intent
-     * @param callingPackage The name of the package requesting the ephemeral application
+     * @param callingPkg The app requesting the ephemeral application
+     * @param isRequesterInstantApp Whether or not the app requesting the ephemeral application
+     *                              is an instant app
      * @param verificationBundle Optional bundle to pass to the installer for additional
      * verification
      * @param userId The ID of the user that triggered ephemeral resolution
      */
     public abstract void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
-            Intent origIntent, String resolvedType, String callingPackage,
-            Bundle verificationBundle, int userId);
+            Intent origIntent, String resolvedType, String callingPkg,
+            boolean isRequesterInstantApp, Bundle verificationBundle, int userId);
 
     /**
      * Grants implicit access based on an interaction between two apps. This grants the target app
diff --git a/services/core/java/android/os/UserManagerInternal.java b/services/core/java/android/os/UserManagerInternal.java
index a2e9341..d84197c 100644
--- a/services/core/java/android/os/UserManagerInternal.java
+++ b/services/core/java/android/os/UserManagerInternal.java
@@ -57,7 +57,7 @@
      * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to set
      * restrictions enforced by the user.
      *
-     * @param userId target user id for the local restrictions.
+     * @param originatingUserId user id of the user where the restriction originated.
      * @param restrictions a bundle of user restrictions.
      * @param restrictionOwnerType determines which admin {@code userId} corresponds to.
      *             The admin can be either
@@ -70,8 +70,8 @@
      *             otherwise it will be applied just on the current user.
      * @see OwnerType
      */
-    public abstract void setDevicePolicyUserRestrictions(int userId, @Nullable Bundle restrictions,
-            @OwnerType int restrictionOwnerType);
+    public abstract void setDevicePolicyUserRestrictions(int originatingUserId,
+            @Nullable Bundle restrictions, @OwnerType int restrictionOwnerType);
 
     /**
      * Returns the "base" user restrictions.
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 1e5b915..73b6c7a 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -22,6 +22,8 @@
 import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.RTC_WAKEUP;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.UserHandle.USER_SYSTEM;
 
 import android.annotation.UserIdInt;
 import android.app.Activity;
@@ -41,10 +43,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionInfo;
+import android.content.pm.PackageManagerInternal;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatteryManager;
@@ -223,15 +222,6 @@
     long mLastTimeChangeRealtime;
     int mNumTimeChanged;
 
-    // Bookkeeping about the identity of the "System UI" package, determined at runtime.
-
-    /**
-     * This permission must be defined by the canonical System UI package,
-     * with protection level "signature".
-     */
-    private static final String SYSTEM_UI_SELF_PERMISSION =
-            "android.permission.systemui.IDENTITY";
-
     /**
      * At boot we use SYSTEM_UI_SELF_PERMISSION to look up the definer's uid.
      */
@@ -3201,7 +3191,7 @@
     }
 
     void removeUserLocked(int userHandle) {
-        if (userHandle == UserHandle.USER_SYSTEM) {
+        if (userHandle == USER_SYSTEM) {
             // If we're told we're removing the system user, ignore it.
             return;
         }
@@ -3845,21 +3835,9 @@
         }
 
         int getSystemUiUid() {
-            int sysUiUid = -1;
-            final PackageManager pm = mContext.getPackageManager();
-            try {
-                PermissionInfo sysUiPerm = pm.getPermissionInfo(SYSTEM_UI_SELF_PERMISSION, 0);
-                ApplicationInfo sysUi = pm.getApplicationInfo(sysUiPerm.packageName, 0);
-                if ((sysUi.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
-                    sysUiUid = sysUi.uid;
-                } else {
-                    Slog.e(TAG, "SysUI permission " + SYSTEM_UI_SELF_PERMISSION
-                            + " defined by non-privileged app " + sysUi.packageName
-                            + " - ignoring");
-                }
-            } catch (NameNotFoundException e) {
-            }
-            return sysUiUid;
+            PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+            return pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
+                    MATCH_SYSTEM_ONLY, USER_SYSTEM);
         }
 
         ClockReceiver getClockReceiver(AlarmManagerService service) {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 119b987..470300e 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -16,6 +16,9 @@
 
 package com.android.server;
 
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.os.UserHandle.USER_SYSTEM;
+
 import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -42,6 +45,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.os.Binder;
@@ -242,10 +246,10 @@
                     }
 
                     // DISALLOW_BLUETOOTH can only be set by DO or PO on the system user.
-                    if (userId == UserHandle.USER_SYSTEM
+                    if (userId == USER_SYSTEM
                             && UserRestrictionsUtils.restrictionsChanged(prevRestrictions,
                             newRestrictions, UserManager.DISALLOW_BLUETOOTH)) {
-                        if (userId == UserHandle.USER_SYSTEM && newRestrictions.getBoolean(
+                        if (userId == USER_SYSTEM && newRestrictions.getBoolean(
                                 UserManager.DISALLOW_BLUETOOTH)) {
                             updateOppLauncherComponentState(userId, true); // Sharing disallowed
                             sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED,
@@ -437,18 +441,18 @@
         }
 
         int systemUiUid = -1;
-        try {
-            // Check if device is configured with no home screen, which implies no SystemUI.
-            boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen);
-            if (!noHome) {
-                systemUiUid = mContext.getPackageManager()
-                        .getPackageUidAsUser("com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
-                                UserHandle.USER_SYSTEM);
-            }
+        // Check if device is configured with no home screen, which implies no SystemUI.
+        boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen);
+        if (!noHome) {
+            PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+            systemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
+                    MATCH_SYSTEM_ONLY, USER_SYSTEM);
+        }
+        if (systemUiUid >= 0) {
             Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid));
-        } catch (PackageManager.NameNotFoundException e) {
+        } else {
             // Some platforms, such as wearables do not have a system ui.
-            Slog.w(TAG, "Unable to resolve SystemUI's UID.", e);
+            Slog.w(TAG, "Unable to resolve SystemUI's UID.");
         }
         mSystemUiUid = systemUiUid;
     }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9b1d9e9..5a78036 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -118,6 +118,7 @@
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.DataUnit;
 import android.util.FeatureFlagUtils;
@@ -3161,16 +3162,20 @@
         final boolean forWrite = (flags & StorageManager.FLAG_FOR_WRITE) != 0;
         final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0;
         final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0;
+        final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0;
 
         // Report all volumes as unmounted until we've recorded that user 0 has unlocked. There
         // are no guarantees that callers will see a consistent view of the volume before that
         // point
         final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM);
 
+        final boolean userIsDemo;
         final boolean userKeyUnlocked;
         final boolean storagePermission;
         final long token = Binder.clearCallingIdentity();
         try {
+            userIsDemo = LocalServices.getService(UserManagerInternal.class)
+                    .getUserInfo(userId).isDemo();
             userKeyUnlocked = isUserKeyUnlocked(userId);
             storagePermission = mStorageManagerInternal.hasExternalStorage(uid, packageName);
         } finally {
@@ -3180,6 +3185,7 @@
         boolean foundPrimary = false;
 
         final ArrayList<StorageVolume> res = new ArrayList<>();
+        final ArraySet<String> resUuids = new ArraySet<>();
         synchronized (mLock) {
             for (int i = 0; i < mVolumes.size(); i++) {
                 final VolumeInfo vol = mVolumes.valueAt(i);
@@ -3222,7 +3228,43 @@
                 } else {
                     res.add(userVol);
                 }
+                resUuids.add(userVol.getUuid());
             }
+
+            if (includeRecent) {
+                final long lastWeek = System.currentTimeMillis() - DateUtils.WEEK_IN_MILLIS;
+                for (int i = 0; i < mRecords.size(); i++) {
+                    final VolumeRecord rec = mRecords.valueAt(i);
+
+                    // Skip if we've already included it above
+                    if (resUuids.contains(rec.fsUuid)) continue;
+
+                    // Treat as recent if mounted within the last week
+                    if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) {
+                        final StorageVolume userVol = rec.buildStorageVolume(mContext);
+                        res.add(userVol);
+                        resUuids.add(userVol.getUuid());
+                    }
+                }
+            }
+        }
+
+        // Synthesize a volume for preloaded media under demo users, so that
+        // it's scanned into MediaStore
+        if (userIsDemo) {
+            final String id = "demo";
+            final File path = Environment.getDataPreloadsMediaDirectory();
+            final boolean primary = false;
+            final boolean removable = false;
+            final boolean emulated = true;
+            final boolean allowMassStorage = false;
+            final long maxFileSize = 0;
+            final UserHandle user = new UserHandle(userId);
+            final String envState = Environment.MEDIA_MOUNTED_READ_ONLY;
+            final String description = mContext.getString(android.R.string.unknownName);
+
+            res.add(new StorageVolume(id, path, path, description, primary, removable,
+                    emulated, allowMassStorage, maxFileSize, user, id, envState));
         }
 
         if (!foundPrimary) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 6bc117b..0e144ed 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -31,7 +31,6 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.LinkProperties;
-import android.net.NetworkCapabilities;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -210,8 +209,6 @@
 
     private int[] mDataConnectionNetworkType;
 
-    private int[] mOtaspMode;
-
     private ArrayList<List<CellInfo>> mCellInfo = null;
 
     private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList;
@@ -260,7 +257,9 @@
 
     private final LocalLog mListenLog = new LocalLog(100);
 
-    private PreciseDataConnectionState[] mPreciseDataConnectionState;
+    // Per-phoneMap of APN Type to DataConnectionState
+    private List<Map<String, PreciseDataConnectionState>> mPreciseDataConnectionStates =
+            new ArrayList<Map<String, PreciseDataConnectionState>>();
 
     // Nothing here yet, but putting it here in case we want to add more in the future.
     static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0;
@@ -405,7 +404,6 @@
         mCallForwarding = copyOf(mCallForwarding, mNumPhones);
         mCellLocation = copyOf(mCellLocation, mNumPhones);
         mSrvccState = copyOf(mSrvccState, mNumPhones);
-        mOtaspMode = copyOf(mOtaspMode, mNumPhones);
         mPreciseCallState = copyOf(mPreciseCallState, mNumPhones);
         mForegroundCallState = copyOf(mForegroundCallState, mNumPhones);
         mBackgroundCallState = copyOf(mBackgroundCallState, mNumPhones);
@@ -415,7 +413,6 @@
         mCallQuality = copyOf(mCallQuality, mNumPhones);
         mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
         mCallAttributes = copyOf(mCallAttributes, mNumPhones);
-        mPreciseDataConnectionState = copyOf(mPreciseDataConnectionState, mNumPhones);
         mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
         mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
 
@@ -423,6 +420,7 @@
         if (mNumPhones < oldNumPhones) {
             cutListToSize(mCellInfo, mNumPhones);
             cutListToSize(mImsReasonInfo, mNumPhones);
+            cutListToSize(mPreciseDataConnectionStates, mNumPhones);
             return;
         }
 
@@ -443,7 +441,6 @@
             mCellInfo.add(i, null);
             mImsReasonInfo.add(i, null);
             mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
-            mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN;
             mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
             mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
             mCallQuality[i] = createCallQuality();
@@ -454,7 +451,7 @@
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
-            mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
+            mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
         }
 
         // Note that location can be null for non-phone builds like
@@ -506,7 +503,6 @@
         mCallForwarding = new boolean[numPhones];
         mCellLocation = new Bundle[numPhones];
         mSrvccState = new int[numPhones];
-        mOtaspMode = new int[numPhones];
         mPreciseCallState = new PreciseCallState[numPhones];
         mForegroundCallState = new int[numPhones];
         mBackgroundCallState = new int[numPhones];
@@ -516,7 +512,7 @@
         mCallQuality = new CallQuality[numPhones];
         mCallNetworkType = new int[numPhones];
         mCallAttributes = new CallAttributes[numPhones];
-        mPreciseDataConnectionState = new PreciseDataConnectionState[numPhones];
+        mPreciseDataConnectionStates = new ArrayList<>();
         mCellInfo = new ArrayList<>();
         mImsReasonInfo = new ArrayList<>();
         mEmergencyNumberList = new HashMap<>();
@@ -538,7 +534,6 @@
             mCellInfo.add(i, null);
             mImsReasonInfo.add(i, null);
             mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
-            mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN;
             mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
             mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
             mCallQuality[i] = createCallQuality();
@@ -549,7 +544,7 @@
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
-            mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
+            mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
         }
 
         // Note that location can be null for non-phone builds like
@@ -880,13 +875,6 @@
                             remove(r.binder);
                         }
                     }
-                    if ((events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) {
-                        try {
-                            r.callback.onOtaspChanged(mOtaspMode[phoneId]);
-                        } catch (RemoteException ex) {
-                            remove(r.binder);
-                        }
-                    }
                     if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
                         try {
                             if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
@@ -922,8 +910,10 @@
                     }
                     if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
                         try {
-                            r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionState[phoneId]);
+                            for (PreciseDataConnectionState pdcs
+                                    : mPreciseDataConnectionStates.get(phoneId).values()) {
+                                r.callback.onPreciseDataConnectionStateChanged(pdcs);
+                            }
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
@@ -937,7 +927,8 @@
                     }
                     if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) !=0) {
                         try {
-                            r.callback.onVoiceActivationStateChanged(mVoiceActivationState[phoneId]);
+                            r.callback.onVoiceActivationStateChanged(
+                                    mVoiceActivationState[phoneId]);
                         } catch (RemoteException ex) {
                             remove(r.binder);
                         }
@@ -1496,30 +1487,38 @@
         }
     }
 
-    public void notifyDataConnection(int state, boolean isDataAllowed, String apn, String apnType,
-                                     LinkProperties linkProperties,
-                                     NetworkCapabilities networkCapabilities, int networkType,
-                                     boolean roaming) {
-        notifyDataConnectionForSubscriber(SubscriptionManager.DEFAULT_PHONE_INDEX,
-                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,  state,
-                isDataAllowed, apn, apnType, linkProperties,
-                networkCapabilities, networkType, roaming);
-    }
-
-    public void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
-                                                  boolean isDataAllowed,
-                                                  String apn, String apnType,
-            LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
-            int networkType, boolean roaming) {
+    /**
+     * Send a notification to registrants that the data connection state has changed.
+     *
+     * @param phoneId the phoneId carrying the data connection
+     * @param subId the subscriptionId for the data connection
+     * @param apnType the APN type that triggered a change in the data connection
+     * @param preciseState a PreciseDataConnectionState that has info about the data connection
+     */
+    public void notifyDataConnectionForSubscriber(
+            int phoneId, int subId, String apnType, PreciseDataConnectionState preciseState) {
         if (!checkNotifyPermission("notifyDataConnection()" )) {
             return;
         }
+
+        String apn = "";
+        int state = TelephonyManager.DATA_UNKNOWN;
+        int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        LinkProperties linkProps = null;
+
+        if (preciseState != null) {
+            apn = preciseState.getDataConnectionApn();
+            state = preciseState.getState();
+            networkType = preciseState.getNetworkType();
+            linkProps = preciseState.getDataConnectionLinkProperties();
+        }
         if (VDBG) {
             log("notifyDataConnectionForSubscriber: subId=" + subId
-                + " state=" + state + " isDataAllowed=" + isDataAllowed
-                + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
-                + " mRecords.size()=" + mRecords.size());
+                    + " state=" + state + "' apn='" + apn
+                    + "' apnType=" + apnType + " networkType=" + networkType
+                    + "' preciseState=" + preciseState);
         }
+
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 // We only call the callback when the change is for default APN type.
@@ -1551,36 +1550,48 @@
                     mDataConnectionState[phoneId] = state;
                     mDataConnectionNetworkType[phoneId] = networkType;
                 }
-                mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
-                        state, networkType,
-                        ApnSetting.getApnTypesBitmaskFromString(apnType), apn,
-                        linkProperties, DataFailCause.NONE);
-                for (Record r : mRecords) {
-                    if (r.matchPhoneStateListenerEvent(
-                            PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
-                            && idMatch(r.subId, subId, phoneId)) {
-                        try {
-                            r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionState[phoneId]);
-                        } catch (RemoteException ex) {
-                            mRemoveList.add(r.binder);
+
+                boolean needsNotify = false;
+                // State has been cleared for this APN Type
+                if (preciseState == null) {
+                    // We try clear the state and check if the state was previously not cleared
+                    needsNotify = mPreciseDataConnectionStates.get(phoneId).remove(apnType) != null;
+                } else {
+                    // We need to check to see if the state actually changed
+                    PreciseDataConnectionState oldPreciseState =
+                            mPreciseDataConnectionStates.get(phoneId).put(apnType, preciseState);
+                    needsNotify = !preciseState.equals(oldPreciseState);
+                }
+
+                if (needsNotify) {
+                    for (Record r : mRecords) {
+                        if (r.matchPhoneStateListenerEvent(
+                                PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
+                                && idMatch(r.subId, subId, phoneId)) {
+                            try {
+                                r.callback.onPreciseDataConnectionStateChanged(preciseState);
+                            } catch (RemoteException ex) {
+                                mRemoveList.add(r.binder);
+                            }
                         }
                     }
                 }
             }
             handleRemoveListLocked();
         }
-        broadcastDataConnectionStateChanged(state, isDataAllowed, apn, apnType, linkProperties,
-                networkCapabilities, roaming, subId);
+
+        broadcastDataConnectionStateChanged(state, apn, apnType, subId);
     }
 
+    /**
+     * Stub to satisfy the ITelephonyRegistry aidl interface; do not use this function.
+     * @see #notifyDataConnectionFailedForSubscriber
+     */
     public void notifyDataConnectionFailed(String apnType) {
-         notifyDataConnectionFailedForSubscriber(SubscriptionManager.DEFAULT_PHONE_INDEX,
-                 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                 apnType);
+        loge("This function should not be invoked");
     }
 
-    public void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
+    private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
         if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
             return;
         }
@@ -1590,17 +1601,20 @@
         }
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
-                mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
-                        TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                        ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
-                        DataFailCause.NONE);
+                mPreciseDataConnectionStates.get(phoneId).put(
+                        apnType,
+                        new PreciseDataConnectionState(
+                                TelephonyManager.DATA_UNKNOWN,
+                                TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                                ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+                                DataFailCause.NONE));
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
                             PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionState[phoneId]);
+                                    mPreciseDataConnectionStates.get(phoneId).get(apnType));
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
@@ -1651,29 +1665,6 @@
         }
     }
 
-    public void notifyOtaspChanged(int subId, int otaspMode) {
-        if (!checkNotifyPermission("notifyOtaspChanged()" )) {
-            return;
-        }
-        int phoneId = SubscriptionManager.getPhoneId(subId);
-        synchronized (mRecords) {
-            if (validatePhoneId(phoneId)) {
-                mOtaspMode[phoneId] = otaspMode;
-                for (Record r : mRecords) {
-                    if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_OTASP_CHANGED)
-                            && idMatch(r.subId, subId, phoneId)) {
-                        try {
-                            r.callback.onOtaspChanged(otaspMode);
-                        } catch (RemoteException ex) {
-                            mRemoveList.add(r.binder);
-                        }
-                    }
-                }
-            }
-            handleRemoveListLocked();
-        }
-    }
-
     public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
                                        int foregroundCallState, int backgroundCallState) {
         if (!checkNotifyPermission("notifyPreciseCallState()")) {
@@ -1788,25 +1779,32 @@
         if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
             return;
         }
+
+        // precise notify invokes imprecise notify
+        notifyDataConnectionFailedForSubscriber(phoneId, subId, apnType);
+
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
-                mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
-                        TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                        ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
+                mPreciseDataConnectionStates.get(phoneId).put(
+                        apnType,
+                        new PreciseDataConnectionState(
+                                TelephonyManager.DATA_UNKNOWN,
+                                TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                                ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+                                failCause));
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
                             PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
                             && idMatch(r.subId, subId, phoneId)) {
                         try {
                             r.callback.onPreciseDataConnectionStateChanged(
-                                    mPreciseDataConnectionState[phoneId]);
+                                    mPreciseDataConnectionStates.get(phoneId).get(apnType));
                         } catch (RemoteException ex) {
                             mRemoveList.add(r.binder);
                         }
                     }
                 }
             }
-
             handleRemoveListLocked();
         }
     }
@@ -2064,7 +2062,6 @@
         }
     }
 
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -2098,12 +2095,11 @@
                 pw.println("mCellInfo=" + mCellInfo.get(i));
                 pw.println("mImsCallDisconnectCause=" + mImsReasonInfo.get(i));
                 pw.println("mSrvccState=" + mSrvccState[i]);
-                pw.println("mOtaspMode=" + mOtaspMode[i]);
                 pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
                 pw.println("mCallQuality=" + mCallQuality[i]);
                 pw.println("mCallAttributes=" + mCallAttributes[i]);
                 pw.println("mCallNetworkType=" + mCallNetworkType[i]);
-                pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState[i]);
+                pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
                 pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
                 pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
                 pw.decreaseIndent();
@@ -2263,29 +2259,13 @@
         }
     }
 
-    private void broadcastDataConnectionStateChanged(int state, boolean isDataAllowed, String apn,
-                                                     String apnType, LinkProperties linkProperties,
-                                                     NetworkCapabilities networkCapabilities,
-                                                     boolean roaming, int subId) {
+    private void broadcastDataConnectionStateChanged(int state, String apn,
+                                                     String apnType, int subId) {
         // Note: not reporting to the battery stats service here, because the
         // status bar takes care of that after taking into account all of the
         // required info.
         Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
         intent.putExtra(TelephonyManager.EXTRA_STATE, dataStateToString(state));
-        if (!isDataAllowed) {
-            intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
-        }
-        if (linkProperties != null) {
-            intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
-            String iface = linkProperties.getInterfaceName();
-            if (iface != null) {
-                intent.putExtra(PhoneConstants.DATA_IFACE_NAME_KEY, iface);
-            }
-        }
-        if (networkCapabilities != null) {
-            intent.putExtra(PhoneConstants.DATA_NETWORK_CAPABILITIES_KEY, networkCapabilities);
-        }
-        if (roaming) intent.putExtra(PhoneConstants.DATA_NETWORK_ROAMING_KEY, true);
 
         intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
         intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 3330882..76a8f92 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.input.InputManager;
@@ -60,6 +61,7 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.DebugUtils;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.StatsLog;
@@ -79,7 +81,6 @@
         implements InputManager.InputDeviceListener {
     private static final String TAG = "VibratorService";
     private static final boolean DEBUG = false;
-    private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
     private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
     private static final String RAMPING_RINGER_ENABLED = "ramping_ringer_enabled";
 
@@ -139,6 +140,7 @@
     private final PowerManager.WakeLock mWakeLock;
     private final AppOpsManager mAppOps;
     private final IBatteryStats mBatteryStatsService;
+    private final String mSystemUiPackage;
     private PowerManagerInternal mPowerManagerInternal;
     private InputManager mIm;
     private Vibrator mVibrator;
@@ -161,6 +163,8 @@
     private int mHapticFeedbackIntensity;
     private int mNotificationIntensity;
     private int mRingIntensity;
+    private SparseArray<Pair<VibrationEffect, AudioAttributes>> mAlwaysOnEffects =
+            new SparseArray<>();
 
     static native boolean vibratorExists();
     static native void vibratorInit();
@@ -172,6 +176,8 @@
     static native boolean vibratorSupportsExternalControl();
     static native void vibratorSetExternalControl(boolean enabled);
     static native long vibratorGetCapabilities();
+    static native void vibratorAlwaysOnEnable(long id, long effect, long strength);
+    static native void vibratorAlwaysOnDisable(long id);
 
     private final IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
@@ -284,7 +290,7 @@
         }
 
         public boolean isFromSystem() {
-            return uid == Process.SYSTEM_UID || uid == 0 || SYSTEM_UI_PACKAGE.equals(opPkg);
+            return uid == Process.SYSTEM_UID || uid == 0 || mSystemUiPackage.equals(opPkg);
         }
 
         public VibrationInfo toInfo() {
@@ -372,6 +378,8 @@
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                 BatteryStats.SERVICE_NAME));
+        mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
+                .getSystemUiServiceComponent().getPackageName();
 
         mPreviousVibrationsLimit = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_previousVibrationsDumpLimit);
@@ -519,6 +527,41 @@
         }
     }
 
+    @Override // Binder call
+    public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attrs) {
+        if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
+            throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
+        }
+        if ((mCapabilities & IVibrator.CAP_ALWAYS_ON_CONTROL) == 0) {
+            Slog.e(TAG, "Always-on effects not supported.");
+            return false;
+        }
+        if (effect == null) {
+            synchronized (mLock) {
+                mAlwaysOnEffects.delete(id);
+                vibratorAlwaysOnDisable(id);
+            }
+        } else {
+            if (!verifyVibrationEffect(effect)) {
+                return false;
+            }
+            if (!(effect instanceof VibrationEffect.Prebaked)) {
+                Slog.e(TAG, "Only prebaked effects supported for always-on.");
+                return false;
+            }
+            if (attrs == null) {
+                attrs = new AudioAttributes.Builder()
+                        .setUsage(AudioAttributes.USAGE_UNKNOWN)
+                        .build();
+            }
+            synchronized (mLock) {
+                mAlwaysOnEffects.put(id, Pair.create(effect, attrs));
+                updateAlwaysOnLocked(id, effect, attrs);
+            }
+        }
+        return true;
+    }
+
     private void verifyIncomingUid(int uid) {
         if (uid == Binder.getCallingUid()) {
             return;
@@ -989,6 +1032,8 @@
                 // If the state changes out from under us then just reset.
                 doCancelVibrateLocked();
             }
+
+            updateAlwaysOnLocked();
         }
     }
 
@@ -1055,6 +1100,27 @@
                 mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
     }
 
+    private void updateAlwaysOnLocked(int id, VibrationEffect effect, AudioAttributes attrs) {
+        // TODO: Check DND and LowPower settings
+        final Vibration vib = new Vibration(null, effect, attrs, 0, null, null);
+        final int intensity = getCurrentIntensityLocked(vib);
+        if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+            vibratorAlwaysOnDisable(id);
+        } else {
+            final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+            final int strength = intensityToEffectStrength(intensity);
+            vibratorAlwaysOnEnable(id, prebaked.getId(), strength);
+        }
+    }
+
+    private void updateAlwaysOnLocked() {
+        for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
+            int id = mAlwaysOnEffects.keyAt(i);
+            Pair<VibrationEffect, AudioAttributes> pair = mAlwaysOnEffects.valueAt(i);
+            updateAlwaysOnLocked(id, pair.first, pair.second);
+        }
+    }
+
     @Override
     public void onInputDeviceAdded(int deviceId) {
         updateVibrators();
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index e101fe0..5996b7d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4409,7 +4409,6 @@
         return true;
     }
 
-    @Override
     public boolean renameSharedAccountAsUser(Account account, String newName, int userId) {
         userId = handleIncomingUser(userId);
         UserAccounts accounts = getUserAccounts(userId);
@@ -4425,7 +4424,6 @@
         return r > 0;
     }
 
-    @Override
     public boolean removeSharedAccountAsUser(Account account, int userId) {
         return removeSharedAccountAsUser(account, userId, getCallingUid());
     }
@@ -4443,7 +4441,6 @@
         return deleted;
     }
 
-    @Override
     public Account[] getSharedAccountsAsUser(int userId) {
         userId = handleIncomingUser(userId);
         UserAccounts accounts = getUserAccounts(userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b55d6ad..f1cee034 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8389,6 +8389,18 @@
         return BugReportHandlerUtil.launchBugReportHandlerApp(mContext);
     }
 
+    /**
+     * Get packages of bugreport-whitelisted apps to handle a bug report.
+     *
+     * @return packages of bugreport-whitelisted apps to handle a bug report.
+     */
+    @Override
+    public List<String> getBugreportWhitelistedPackages() {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_DEBUGGING,
+                "getBugreportWhitelistedPackages");
+        return new ArrayList<>(SystemConfig.getInstance().getBugreportWhitelistedPackages());
+    }
+
     public void registerProcessObserver(IProcessObserver observer) {
         enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerProcessObserver()");
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 5378f43..ee06419 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -461,9 +461,7 @@
 
         @GuardedBy("ProcessList.this.mService")
         void freeIsolatedUidLocked(int uid) {
-            // Strip out userId
-            final int appId = UserHandle.getAppId(uid);
-            mUidUsed.delete(appId);
+            mUidUsed.delete(uid);
         }
     };
 
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 232bc08e..fc67e24 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -77,21 +77,28 @@
     private static final String LOG_TAG = "AttentionManagerService";
     private static final boolean DEBUG = false;
 
-    /** Default value in absence of {@link DeviceConfig} override. */
-    private static final boolean DEFAULT_SERVICE_ENABLED = true;
-
     /** Service will unbind if connection is not used for that amount of time. */
     private static final long CONNECTION_TTL_MILLIS = 60_000;
 
-    /** If the check attention called within that period - cached value will be returned. */
-    private static final long STALE_AFTER_MILLIS = 5_000;
+    /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
+    private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    private static final boolean DEFAULT_SERVICE_ENABLED = true;
+
+    /**
+     * DeviceConfig flag name, describes how much time we consider a result fresh; if the check
+     * attention called within that period - cached value will be returned.
+     */
+    @VisibleForTesting static final String KEY_STALE_AFTER_MILLIS = "stale_after_millis";
+
+    /** Default value in absence of {@link DeviceConfig} override. */
+    @VisibleForTesting static final long DEFAULT_STALE_AFTER_MILLIS = 1_000;
 
     /** The size of the buffer that stores recent attention check results. */
     @VisibleForTesting
     protected static final int ATTENTION_CACHE_BUFFER_SIZE = 5;
 
-    /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
-    private static final String SERVICE_ENABLED = "service_enabled";
     private static String sTestAttentionServicePackage;
     private final Context mContext;
     private final PowerManager mPowerManager;
@@ -160,11 +167,29 @@
 
     @VisibleForTesting
     protected boolean isServiceEnabled() {
-        return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, SERVICE_ENABLED,
+        return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_SERVICE_ENABLED,
                 DEFAULT_SERVICE_ENABLED);
     }
 
     /**
+     * How much time we consider a result fresh; if the check attention called within that period -
+     * cached value will be returned.
+     */
+    @VisibleForTesting
+    protected long getStaleAfterMillis() {
+        final long millis = DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS,
+                DEFAULT_STALE_AFTER_MILLIS);
+
+        if (millis < 0 || millis > 10_000) {
+            Slog.w(LOG_TAG, "Bad flag value supplied for: " + KEY_STALE_AFTER_MILLIS);
+            return DEFAULT_STALE_AFTER_MILLIS;
+        }
+
+        return millis;
+    }
+
+    /**
      * Checks whether user attention is at the screen and calls in the provided callback.
      *
      * Calling this multiple times quickly in a row will result in either a) returning a cached
@@ -199,7 +224,7 @@
             // throttle frequent requests
             final AttentionCheckCache cache = userState.mAttentionCheckCacheBuffer == null ? null
                     : userState.mAttentionCheckCacheBuffer.getLast();
-            if (cache != null && now < cache.mLastComputed + STALE_AFTER_MILLIS) {
+            if (cache != null && now < cache.mLastComputed + getStaleAfterMillis()) {
                 callbackInternal.onSuccess(cache.mResult, cache.mTimestamp);
                 return true;
             }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 8144a71..60f420e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -600,19 +600,11 @@
         sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
     }
 
-    /*package*/ void cancelA2dpDockTimeout() {
-        mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
-    }
-
     /*package*/ void postA2dpActiveDeviceChange(
                     @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
         sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
     }
 
-    /*package*/ boolean hasScheduledA2dpDockTimeout() {
-        return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
-    }
-
     // must be called synchronized on mConnectedDevices
     /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
         return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
@@ -621,8 +613,8 @@
                         new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
     }
 
-    /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
-        sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
+    /*package*/ void setA2dpTimeout(String address, int a2dpCodec, int delayMs) {
+        sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
     }
 
     /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
@@ -781,7 +773,7 @@
                         }
                     }
                     break;
-                case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+                case MSG_IL_BTA2DP_TIMEOUT:
                     // msg.obj  == address of BTA2DP device
                     synchronized (mDeviceStateLock) {
                         mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
@@ -945,7 +937,7 @@
     private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
     private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
     private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
-    private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10;
+    private static final int MSG_IL_BTA2DP_TIMEOUT = 10;
     private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
     private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
     private static final int MSG_REPORT_NEW_ROUTES = 13;
@@ -981,7 +973,7 @@
             case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
             case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
             case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
-            case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+            case MSG_IL_BTA2DP_TIMEOUT:
             case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
             case MSG_TOGGLE_HDMI:
             case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
@@ -1071,7 +1063,7 @@
                 case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
                 case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
-                case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+                case MSG_IL_BTA2DP_TIMEOUT:
                 case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
                 case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
                     if (sLastDeviceConnectMsgTime >= time) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 9061586..df56004 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -59,10 +59,21 @@
 
     private static final String TAG = "AS.AudioDeviceInventory";
 
-    // Actual list of connected devices
+    // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
+    private final Object mDevicesLock = new Object();
+
+    // List of connected devices
     // Key for map created from DeviceInfo.makeDeviceListKey()
+    @GuardedBy("mDevicesLock")
     private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>();
 
+    // List of devices actually connected to AudioPolicy (through AudioSystem), only one
+    // by device type, which is used as the key, value is the DeviceInfo generated key.
+    // For the moment only for A2DP sink devices.
+    // TODO: extend to all device types
+    @GuardedBy("mDevicesLock")
+    private final ArrayMap<Integer, String> mApmConnectedDevices = new ArrayMap<>();
+
     // List of preferred devices for strategies
     private final ArrayMap<Integer, AudioDeviceAddress> mPreferredDevices = new ArrayMap<>();
 
@@ -94,25 +105,30 @@
      */
     private static class DeviceInfo {
         final int mDeviceType;
-        final String mDeviceName;
-        final String mDeviceAddress;
+        final @NonNull String mDeviceName;
+        final @NonNull String mDeviceAddress;
         int mDeviceCodecFormat;
 
         DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
             mDeviceType = deviceType;
-            mDeviceName = deviceName;
-            mDeviceAddress = deviceAddress;
+            mDeviceName = deviceName == null ? "" : deviceName;
+            mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
             mDeviceCodecFormat = deviceCodecFormat;
         }
 
         @Override
         public String toString() {
             return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
-                    + " name:" + mDeviceName
+                    + " (" + AudioSystem.getDeviceName(mDeviceType)
+                    + ") name:" + mDeviceName
                     + " addr:" + mDeviceAddress
                     + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
         }
 
+        String getKey() {
+            return makeDeviceListKey(mDeviceType, mDeviceAddress);
+        }
+
         /**
          * Generate a unique key for the mConnectedDevices List by composing the device "type"
          * and the "address" associated with a specific instance of that device type
@@ -147,6 +163,14 @@
         pw.println("\n" + prefix + "Preferred devices for strategy:");
         mPreferredDevices.forEach((strategy, device) -> {
             pw.println("  " + prefix + "strategy:" + strategy + " device:" + device); });
+        pw.println("\n" + prefix + "Connected devices:");
+        mConnectedDevices.forEach((key, deviceInfo) -> {
+            pw.println("  " + prefix + deviceInfo.toString()); });
+        pw.println("\n" + prefix + "APM Connected device (A2DP sink only):");
+        mApmConnectedDevices.forEach((keyType, valueAddress) -> {
+            pw.println("  " + prefix + " type:0x" + Integer.toHexString(keyType)
+                    + " (" + AudioSystem.getDeviceName(keyType)
+                    + ") addr:" + valueAddress); });
     }
 
     //------------------------------------------------------------
@@ -158,7 +182,8 @@
      */
     // Always executed on AudioDeviceBroker message queue
     /*package*/ void onRestoreDevices() {
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
+            //TODO iterate on mApmConnectedDevices instead once it handles all device types
             for (DeviceInfo di : mConnectedDevices.values()) {
                 AudioSystem.setDeviceConnectionState(
                         di.mDeviceType,
@@ -168,7 +193,6 @@
                         di.mDeviceCodecFormat);
             }
         }
-
         synchronized (mPreferredDevices) {
             mPreferredDevices.forEach((strategy, device) -> {
                 AudioSystem.setPreferredDeviceForStrategy(strategy, device); });
@@ -187,6 +211,9 @@
                     + state + " vol=" + a2dpVolume);
         }
         String address = btDevice.getAddress();
+        if (address == null) {
+            address = "";
+        }
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
@@ -198,7 +225,7 @@
                         + " codec=" + a2dpCodec
                         + " vol=" + a2dpVolume));
 
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                     btDevice.getAddress());
             final DeviceInfo di = mConnectedDevices.get(key);
@@ -238,7 +265,7 @@
             address = "";
         }
 
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             final String key = DeviceInfo.makeDeviceListKey(
                     AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
             final DeviceInfo di = mConnectedDevices.get(key);
@@ -261,7 +288,7 @@
         AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
                 "onSetHearingAidConnectionState addr=" + address));
 
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
                     btDevice.getAddress());
             final DeviceInfo di = mConnectedDevices.get(key);
@@ -297,7 +324,7 @@
                 "onBluetoothA2dpActiveDeviceChange addr=" + address
                     + " event=" + BtHelper.a2dpDeviceEventToString(event)));
 
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
                         "A2dp config change ignored (scheduled connection change)"));
@@ -340,7 +367,7 @@
     }
 
     /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             makeA2dpDeviceUnavailableNow(address, a2dpCodec);
         }
     }
@@ -377,7 +404,7 @@
                             AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
         AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
 
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
                     && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
                 mDeviceBroker.setBluetoothA2dpOnInt(true,
@@ -405,7 +432,7 @@
     }
 
     /*package*/ void onToggleHdmi() {
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             // Is HDMI connected?
             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
             final DeviceInfo di = mConnectedDevices.get(key);
@@ -472,7 +499,7 @@
                     + Integer.toHexString(device) + " address:" + address
                     + " name:" + deviceName + ")");
         }
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
             if (AudioService.DEBUG_DEVICES) {
                 Slog.i(TAG, "deviceKey:" + deviceKey);
@@ -511,7 +538,7 @@
 
 
     /*package*/ void disconnectA2dp() {
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             final ArraySet<String> toRemove = new ArraySet<>();
             // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
             mConnectedDevices.values().forEach(deviceInfo -> {
@@ -531,7 +558,7 @@
     }
 
     /*package*/ void disconnectA2dpSink() {
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             final ArraySet<String> toRemove = new ArraySet<>();
             // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
             mConnectedDevices.values().forEach(deviceInfo -> {
@@ -544,7 +571,7 @@
     }
 
     /*package*/ void disconnectHearingAid() {
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             final ArraySet<String> toRemove = new ArraySet<>();
             // Disconnect ALL DEVICE_OUT_HEARING_AID devices
             mConnectedDevices.values().forEach(deviceInfo -> {
@@ -568,7 +595,7 @@
     // from AudioSystem
     /*package*/ int checkSendBecomingNoisyIntent(int device,
             @AudioService.ConnectionState int state, int musicDevice) {
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
         }
     }
@@ -595,7 +622,7 @@
         if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
             throw new IllegalArgumentException("invalid profile " + profile);
         }
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
                 @AudioService.ConnectionState int asState =
                         (state == BluetoothA2dp.STATE_CONNECTED)
@@ -635,7 +662,7 @@
 
     /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
                                                   String address, String name, String caller) {
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
             mDeviceBroker.postSetWiredDeviceConnectionState(
                     new WiredDeviceConnectionState(type, state, address, name, caller),
@@ -648,7 +675,7 @@
             @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
             boolean suppressNoisyIntent, int musicDevice) {
         int delay;
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             if (!suppressNoisyIntent) {
                 int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
                 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
@@ -665,39 +692,58 @@
     //-------------------------------------------------------------------
     // Internal utilities
 
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
             int a2dpCodec) {
         // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
         // audio policy manager
         mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
+        // at this point there could be another A2DP device already connected in APM, but it
+        // doesn't matter as this new one will overwrite the previous one
         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
         // Reset A2DP suspend state each time a new sink is connected
         AudioSystem.setParameters("A2dpSuspended=false");
-        mConnectedDevices.put(
-                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
-                new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
-                        address, a2dpCodec));
+
+        final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
+                address, a2dpCodec);
+        final String diKey = di.getKey();
+        mConnectedDevices.put(diKey, di);
+        // on a connection always overwrite the device seen by AudioPolicy, see comment above when
+        // calling AudioSystem
+        mApmConnectedDevices.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, diKey);
+
         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
         setCurrentAudioRouteNameIfPossible(name);
     }
 
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
         if (address == null) {
             return;
         }
+        final String deviceToRemoveKey =
+                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
+
+        mConnectedDevices.remove(deviceToRemoveKey);
+        if (!mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)
+                .equals(deviceToRemoveKey)) {
+            // removing A2DP device not currently used by AudioPolicy, log but don't act on it
+            AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+                    "A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
+            return;
+        }
+
+        // device to remove was visible by APM, update APM
         mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
-        mConnectedDevices.remove(
-                DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+        mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null);
     }
 
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
         // prevent any activity on the A2DP audio output to avoid unwanted
         // reconnection of the sink.
@@ -711,11 +757,11 @@
         // the device will be made unavailable later, so consider it disconnected right away
         mConnectedDevices.remove(deviceKey);
         // send the delayed message to make the device unavailable later
-        mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
+        mDeviceBroker.setA2dpTimeout(address, a2dpCodec, delayMs);
     }
 
 
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private void makeA2dpSrcAvailable(String address) {
         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
@@ -726,7 +772,7 @@
                         address, AudioSystem.AUDIO_FORMAT_DEFAULT));
     }
 
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private void makeA2dpSrcUnavailable(String address) {
         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
@@ -735,7 +781,7 @@
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
     }
 
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private void makeHearingAidDeviceAvailable(
             String address, String name, int streamType, String eventSource) {
         final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
@@ -755,7 +801,7 @@
         setCurrentAudioRouteNameIfPossible(name);
     }
 
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private void makeHearingAidDeviceUnavailable(String address) {
         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
@@ -766,7 +812,7 @@
         setCurrentAudioRouteNameIfPossible(null);
     }
 
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private void setCurrentAudioRouteNameIfPossible(String name) {
         synchronized (mCurAudioRoutes) {
             if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
@@ -779,7 +825,7 @@
         }
     }
 
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private boolean isCurrentDeviceConnected() {
         return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
             TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
@@ -807,7 +853,7 @@
     // must be called before removing the device from mConnectedDevices
     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
     // from AudioSystem
-    @GuardedBy("mConnectedDevices")
+    @GuardedBy("mDevicesLock")
     private int checkSendBecomingNoisyIntentInt(int device,
             @AudioService.ConnectionState int state, int musicDevice) {
         if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
@@ -1015,7 +1061,7 @@
     public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
         final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 device.getAddress());
-        synchronized (mConnectedDevices) {
+        synchronized (mDevicesLock) {
             return (mConnectedDevices.get(key) != null);
         }
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1a62eb2..335cac8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4488,12 +4488,13 @@
         }
 
         if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+                && !isStreamMutedByRingerOrZenMode(AudioSystem.STREAM_MUSIC)
                 && DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.contains(newDevice)
                 && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
                 && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
                 && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0) {
             if (DEBUG_VOL) {
-                Log.i(TAG, String.format(" onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
+                Log.i(TAG, String.format("onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
                         newDevice, AudioSystem.getOutputDeviceName(newDevice)));
             }
             mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
@@ -6167,6 +6168,7 @@
         pw.println("\nRinger mode: ");
         pw.println("- mode (internal) = " + RINGER_MODE_NAMES[mRingerMode]);
         pw.println("- mode (external) = " + RINGER_MODE_NAMES[mRingerModeExternal]);
+        pw.println("- zen mode:" + Settings.Global.zenModeToString(mNm.getZenMode()));
         dumpRingerModeStreams(pw, "affected", mRingerModeAffectedStreams);
         dumpRingerModeStreams(pw, "muted", mRingerAndZenModeMutedStreams);
         pw.print("- delegate = "); pw.println(mRingerModeDelegate);
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 22cb507..0d88388 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -26,10 +26,12 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.IAuthService;
+import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -43,6 +45,7 @@
 import android.os.UserHandle;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.SystemService;
 import com.android.server.biometrics.face.FaceAuthenticator;
@@ -57,9 +60,6 @@
     private static final String TAG = "AuthService";
     private static final boolean DEBUG = false;
 
-    private final boolean mHasFeatureFace;
-    private final boolean mHasFeatureFingerprint;
-    private final boolean mHasFeatureIris;
     private final Injector mInjector;
 
     private IBiometricService mBiometricService;
@@ -89,6 +89,16 @@
         public void publishBinderService(AuthService service, IAuthService.Stub impl) {
             service.publishBinderService(Context.AUTH_SERVICE, impl);
         }
+
+        /**
+         * Allows to test with various device sensor configurations.
+         * @param context
+         * @return
+         */
+        @VisibleForTesting
+        public String[] getConfiguration(Context context) {
+            return context.getResources().getStringArray(R.array.config_biometric_sensors);
+        }
     }
 
     private final class AuthServiceImpl extends IAuthService.Stub {
@@ -119,17 +129,17 @@
         }
 
         @Override
-        public int canAuthenticate(String opPackageName, int userId) throws RemoteException {
+        public int canAuthenticate(String opPackageName, int userId,
+                @Authenticators.Types int authenticators) throws RemoteException {
             final int callingUserId = UserHandle.getCallingUserId();
-            Slog.d(TAG, "canAuthenticate, userId: " + userId
-                    + ", callingUserId: " + callingUserId);
-
+            Slog.d(TAG, "canAuthenticate, userId: " + userId + ", callingUserId: " + callingUserId
+                    + ", authenticators: " + authenticators);
             if (userId != callingUserId) {
                 checkInternalPermission();
             } else {
                 checkPermission();
             }
-            return mBiometricService.canAuthenticate(opPackageName, userId);
+            return mBiometricService.canAuthenticate(opPackageName, userId, authenticators);
         }
 
         @Override
@@ -169,47 +179,56 @@
         mInjector = injector;
         mImpl = new AuthServiceImpl();
         final PackageManager pm = context.getPackageManager();
-        mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
-        mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
-        mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
+    }
+
+    private void registerAuthenticator(SensorConfig config) throws RemoteException {
+
+        Slog.d(TAG, "Registering ID: " + config.mId
+                + " Modality: " + config.mModality
+                + " Strength: " + config.mStrength);
+
+        final IBiometricAuthenticator.Stub authenticator;
+
+        switch (config.mModality) {
+            case TYPE_FINGERPRINT:
+                authenticator = new FingerprintAuthenticator(IFingerprintService.Stub.asInterface(
+                        ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
+                break;
+
+            case TYPE_FACE:
+                authenticator = new FaceAuthenticator(IFaceService.Stub.asInterface(
+                        ServiceManager.getService(Context.FACE_SERVICE)));
+                break;
+
+            case TYPE_IRIS:
+                authenticator = new IrisAuthenticator(IIrisService.Stub.asInterface(
+                        ServiceManager.getService(Context.IRIS_SERVICE)));
+                break;
+
+            default:
+                Slog.e(TAG, "Unknown modality: " + config.mModality);
+                return;
+        }
+
+        mBiometricService.registerAuthenticator(config.mId, config.mModality, config.mStrength,
+                authenticator);
     }
 
     @Override
     public void onStart() {
         mBiometricService = mInjector.getBiometricService();
 
-        if (mHasFeatureFace) {
-            final FaceAuthenticator faceAuthenticator = new FaceAuthenticator(
-                    IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE)));
+        final String[] configs = mInjector.getConfiguration(getContext());
+
+        for (int i = 0; i < configs.length; i++) {
             try {
-                // TODO(b/141025588): Pass down the real id, strength, and modality.
-                mBiometricService.registerAuthenticator(0, 0, TYPE_FACE, faceAuthenticator);
+                registerAuthenticator(new SensorConfig(configs[i]));
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote exception", e);
             }
+
         }
-        if (mHasFeatureFingerprint) {
-            final FingerprintAuthenticator fingerprintAuthenticator = new FingerprintAuthenticator(
-                    IFingerprintService.Stub.asInterface(
-                            ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
-            try {
-                // TODO(b/141025588): Pass down the real id, strength, and modality.
-                mBiometricService.registerAuthenticator(1, 0, TYPE_FINGERPRINT,
-                        fingerprintAuthenticator);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception", e);
-            }
-        }
-        if (mHasFeatureIris) {
-            final IrisAuthenticator irisAuthenticator = new IrisAuthenticator(
-                    IIrisService.Stub.asInterface(ServiceManager.getService(Context.IRIS_SERVICE)));
-            try {
-                // TODO(b/141025588): Pass down the real id, strength, and modality.
-                mBiometricService.registerAuthenticator(2, 0, TYPE_IRIS, irisAuthenticator);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception", e);
-            }
-        }
+
         mInjector.publishBinderService(this, mImpl);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 5d36793..0f51e39 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -23,15 +23,16 @@
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricManager.Authenticators;
 
 import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.app.UserSwitchObserver;
+import android.app.trust.ITrustManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
-import android.hardware.biometrics.Authenticator;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
@@ -78,7 +79,7 @@
  */
 public class BiometricService extends SystemService {
 
-    private static final String TAG = "BiometricService";
+    static final String TAG = "BiometricService";
     private static final boolean DEBUG = true;
 
     private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
@@ -220,11 +221,15 @@
     IStatusBarService mStatusBarService;
     @VisibleForTesting
     KeyStore mKeyStore;
+    @VisibleForTesting
+    ITrustManager mTrustManager;
 
     // Get and cache the available authenticator (manager) classes. Used since aidl doesn't support
     // polymorphism :/
     final ArrayList<AuthenticatorWrapper> mAuthenticators = new ArrayList<>();
 
+    BiometricStrengthController mBiometricStrengthController;
+
     // The current authentication session, null if idle/done. We need to track both the current
     // and pending sessions since errors may be sent to either.
     @VisibleForTesting
@@ -345,17 +350,50 @@
     @VisibleForTesting
     public static final class AuthenticatorWrapper {
         public final int id;
-        public final int strength;
+        public final int OEMStrength; // strength as configured by the OEM
+        private int updatedStrength; // strength updated by BiometricStrengthController
         public final int modality;
         public final IBiometricAuthenticator impl;
 
-        AuthenticatorWrapper(int id, int strength, int modality,
+        AuthenticatorWrapper(int id, int modality, int strength,
                 IBiometricAuthenticator impl) {
             this.id = id;
-            this.strength = strength;
             this.modality = modality;
+            this.OEMStrength = strength;
+            this.updatedStrength = strength;
             this.impl = impl;
         }
+
+        /**
+         * Returns the actual strength, taking any updated strengths into effect. Since more bits
+         * means lower strength, the resulting strength is never stronger than the OEM's configured
+         * strength.
+         * @return a bitfield, see {@link Authenticators}
+         */
+        public int getActualStrength() {
+            return OEMStrength | updatedStrength;
+        }
+
+        /**
+         * Stores the updated strength, which takes effect whenever {@link #getActualStrength()}
+         * is checked.
+         * @param newStrength
+         */
+        public void updateStrength(int newStrength) {
+            String log = "updateStrength: Before(" + toString() + ")";
+            updatedStrength = newStrength;
+            log += " After(" + toString() + ")";
+            Slog.d(TAG, log);
+        }
+
+        @Override
+        public String toString() {
+            return "ID(" + id + ")"
+                    + " OEMStrength: " + OEMStrength
+                    + " updatedStrength: " + updatedStrength
+                    + " modality " + modality
+                    + " authenticator: " + impl;
+        }
     }
 
     @VisibleForTesting
@@ -606,14 +644,14 @@
                 return;
             }
 
-            if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) {
-                checkInternalPermission();
+            if (!Utils.isValidAuthenticatorConfig(bundle)) {
+                throw new SecurityException("Invalid authenticator configuration");
             }
 
             Utils.combineAuthenticatorBundles(bundle);
 
-            // Check the usage of this in system server. Need to remove this check if it becomes
-            // a public API.
+            // Check the usage of this in system server. Need to remove this check if it becomes a
+            // public API.
             final boolean useDefaultTitle =
                     bundle.getBoolean(BiometricPrompt.KEY_USE_DEFAULT_TITLE, false);
             if (useDefaultTitle) {
@@ -651,9 +689,11 @@
         }
 
         @Override // Binder call
-        public int canAuthenticate(String opPackageName, int userId) {
+        public int canAuthenticate(String opPackageName, int userId,
+                @Authenticators.Types int authenticators) {
             Slog.d(TAG, "canAuthenticate: User=" + userId
-                    + ", Caller=" + UserHandle.getCallingUserId());
+                    + ", Caller=" + UserHandle.getCallingUserId()
+                    + ", Authenticators=" + authenticators);
 
             if (userId != UserHandle.getCallingUserId()) {
                 checkInternalPermission();
@@ -661,16 +701,39 @@
                 checkPermission();
             }
 
+
+            if (!Utils.isValidAuthenticatorConfig(authenticators)) {
+                throw new SecurityException("Invalid authenticator configuration");
+            }
+
+            final Bundle bundle = new Bundle();
+            bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+
+            int biometricConstantsResult = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
             final long ident = Binder.clearCallingIdentity();
-            int error;
             try {
-                final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
-                        opPackageName);
-                error = result.second;
+                biometricConstantsResult =
+                        checkAndGetAuthenticators(userId, bundle, opPackageName).second;
+                if (biometricConstantsResult != BiometricConstants.BIOMETRIC_SUCCESS
+                        && Utils.isDeviceCredentialAllowed(bundle)) {
+                    // If there's an issue with biometrics, but device credential is allowed and
+                    // set up, return SUCCESS. If device credential isn't set up either, return
+                    // ERROR_NO_DEVICE_CREDENTIAL.
+                    if (mTrustManager.isDeviceSecure(userId)) {
+                        biometricConstantsResult = BiometricConstants.BIOMETRIC_SUCCESS;
+                    } else {
+                        biometricConstantsResult =
+                                BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL;
+                    }
+                }
+
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
-            return error;
+
+            return Utils.biometricConstantsToBiometricManager(biometricConstantsResult);
         }
 
         @Override
@@ -693,11 +756,46 @@
         }
 
         @Override
-        public void registerAuthenticator(int id, int strength, int modality,
+        public void registerAuthenticator(int id, int modality, int strength,
                 IBiometricAuthenticator authenticator) {
             checkInternalPermission();
 
-            mAuthenticators.add(new AuthenticatorWrapper(id, strength, modality, authenticator));
+            Slog.d(TAG, "Registering ID: " + id
+                    + " Modality: " + modality
+                    + " Strength: " + strength);
+
+            if (authenticator == null) {
+                throw new IllegalArgumentException("Authenticator must not be null."
+                        + " Did you forget to modify the core/res/res/values/xml overlay for"
+                        + " config_biometric_sensors?");
+            }
+
+            if (strength != Authenticators.BIOMETRIC_STRONG
+                    && strength != Authenticators.BIOMETRIC_WEAK) {
+                throw new IllegalStateException("Unsupported strength");
+            }
+
+            for (AuthenticatorWrapper wrapper : mAuthenticators) {
+                if (wrapper.id == id) {
+                    throw new IllegalStateException("Cannot register duplicate authenticator");
+                }
+            }
+
+            // This happens infrequently enough, not worth caching.
+            final String[] configs = mInjector.getConfiguration(getContext());
+            boolean idFound = false;
+            for (int i = 0; i < configs.length; i++) {
+                SensorConfig config = new SensorConfig(configs[i]);
+                if (config.mId == id) {
+                    idFound = true;
+                    break;
+                }
+            }
+            if (!idFound) {
+                throw new IllegalStateException("Cannot register unknown id");
+            }
+
+            mAuthenticators.add(new AuthenticatorWrapper(id, modality, strength, authenticator));
         }
 
         @Override // Binder call
@@ -771,6 +869,11 @@
         }
 
         @VisibleForTesting
+        public ITrustManager getTrustManager() {
+            return ITrustManager.Stub.asInterface(ServiceManager.getService(Context.TRUST_SERVICE));
+        }
+
+        @VisibleForTesting
         public IStatusBarService getStatusBarService() {
             return IStatusBarService.Stub.asInterface(
                     ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -805,6 +908,25 @@
         public void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
             service.publishBinderService(Context.BIOMETRIC_SERVICE, impl);
         }
+
+        /**
+         * Allows to mock BiometricStrengthController for testing.
+         */
+        @VisibleForTesting
+        public BiometricStrengthController getBiometricStrengthController(
+                BiometricService service) {
+            return new BiometricStrengthController(service);
+        }
+
+        /**
+         * Allows to test with various device sensor configurations.
+         * @param context System Server context
+         * @return the sensor configuration from core/res/res/values/config.xml
+         */
+        @VisibleForTesting
+        public String[] getConfiguration(Context context) {
+            return context.getResources().getStringArray(R.array.config_biometric_sensors);
+        }
     }
 
     /**
@@ -849,7 +971,10 @@
     public void onStart() {
         mKeyStore = mInjector.getKeyStore();
         mStatusBarService = mInjector.getStatusBarService();
+        mTrustManager = mInjector.getTrustManager();
         mInjector.publishBinderService(this, mImpl);
+        mBiometricStrengthController = mInjector.getBiometricStrengthController(this);
+        mBiometricStrengthController.startListening();
     }
 
     /**
@@ -857,25 +982,36 @@
      * returns errors through the callback (no biometric feature, hardware not detected, no
      * templates enrolled, etc). This service must not start authentication if errors are sent.
      *
-     * @Returns A pair [Modality, Error] with Modality being one of
+     * @param userId the user to check for
+     * @param bundle passed from {@link BiometricPrompt}
+     * @param opPackageName see {@link android.app.AppOpsManager}
+     *
+     * @return A pair [Modality, Error] with Modality being one of
      * {@link BiometricAuthenticator#TYPE_NONE},
      * {@link BiometricAuthenticator#TYPE_FINGERPRINT},
      * {@link BiometricAuthenticator#TYPE_IRIS},
      * {@link BiometricAuthenticator#TYPE_FACE}
      * and the error containing one of the {@link BiometricConstants} errors.
+     *
+     * TODO(kchyn): Update this to handle DEVICE_CREDENTIAL better, reduce duplicate code in callers
      */
-    private Pair<Integer, Integer> checkAndGetBiometricModality(int userId, String opPackageName) {
-        // No biometric features, send error
-        if (mAuthenticators.isEmpty()) {
-            return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
+    private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle,
+            String opPackageName) throws RemoteException {
+        if (!Utils.isBiometricAllowed(bundle)
+                && Utils.isDeviceCredentialAllowed(bundle)
+                && !mTrustManager.isDeviceSecure(userId)) {
+            // If only device credential is being checked, and the user doesn't have one set up
+            return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL);
         }
 
         // Assuming that authenticators are listed in priority-order, the rest of this function
-        // will go through and find the first authenticator that's available, enrolled, and enabled.
-        // The tricky part is returning the correct error. Error strings that are modality-specific
-        // should also respect the priority-order.
+        // will attempt to find the first authenticator that's as strong or stronger than the
+        // requested strength, available, enrolled, and enabled. The tricky part is returning the
+        // correct error. Error strings that are modality-specific should also respect the
+        // priority-order.
 
-        // Find first authenticator that's detected, enrolled, and enabled.
+        // Find first authenticator that's strong enough, detected, enrolled, and enabled.
+        boolean hasSufficientStrength = false;
         boolean isHardwareDetected = false;
         boolean hasTemplatesEnrolled = false;
         boolean enabledForApps = false;
@@ -883,13 +1019,16 @@
         int modality = TYPE_NONE;
         int firstHwAvailable = TYPE_NONE;
         for (AuthenticatorWrapper authenticator : mAuthenticators) {
-            modality = authenticator.modality;
-            try {
+            final int actualStrength = authenticator.getActualStrength();
+            final int requestedStrength = Utils.getPublicBiometricStrength(bundle);
+            if (Utils.isAtLeastStrength(actualStrength, requestedStrength)) {
+                hasSufficientStrength = true;
+                modality = authenticator.modality;
                 if (authenticator.impl.isHardwareDetected(opPackageName)) {
                     isHardwareDetected = true;
                     if (firstHwAvailable == TYPE_NONE) {
-                        // Store the first one since we want to return the error in correct priority
-                        // order.
+                        // Store the first one since we want to return the error in correct
+                        // priority order.
                         firstHwAvailable = modality;
                     }
                     if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
@@ -900,18 +1039,18 @@
                         }
                     }
                 }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception", e);
             }
         }
 
-        Slog.d(TAG, "checkAndGetBiometricModality: user=" + userId
+        Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId
                 + " isHardwareDetected=" + isHardwareDetected
                 + " hasTemplatesEnrolled=" + hasTemplatesEnrolled
                 + " enabledForApps=" + enabledForApps);
 
         // Check error conditions
-        if (!isHardwareDetected) {
+        if (!hasSufficientStrength) {
+            return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
+        } else if (!isHardwareDetected) {
             return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
         } else if (!hasTemplatesEnrolled) {
             // Return the modality here so the correct error string can be sent. This error is
@@ -1107,13 +1246,16 @@
                         // SystemUI handles transition from biometric to device credential.
                         mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
                         mStatusBarService.onBiometricError(modality, error, vendorCode);
+                    } else if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
+                        mStatusBarService.hideAuthenticationDialog();
+                        // TODO: If multiple authenticators are simultaneously running, this will
+                        // need to be modified. Send the error to the client here, instead of doing
+                        // a round trip to SystemUI.
+                        mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode);
+                        mCurrentAuthSession = null;
                     } else {
                         mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
-                        if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
-                            mStatusBarService.hideAuthenticationDialog();
-                        } else {
-                            mStatusBarService.onBiometricError(modality, error, vendorCode);
-                        }
+                        mStatusBarService.onBiometricError(modality, error, vendorCode);
                     }
                 } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
                     // In the "try again" state, we should forward canceled errors to
@@ -1135,10 +1277,11 @@
                     // If any error is received while preparing the auth session (lockout, etc),
                     // and if device credential is allowed, just show the credential UI.
                     if (mPendingAuthSession.isAllowDeviceCredential()) {
-                        int authenticators = mPendingAuthSession.mBundle
-                                .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
+                        @Authenticators.Types int authenticators =
+                                mPendingAuthSession.mBundle.getInt(
+                                        BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
                         // Disallow biometric and notify SystemUI to show the authentication prompt.
-                        authenticators &= ~Authenticator.TYPE_BIOMETRIC;
+                        authenticators &= ~Authenticators.BIOMETRIC_WEAK;
                         mPendingAuthSession.mBundle.putInt(
                                 BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
                                 authenticators);
@@ -1355,31 +1498,43 @@
             int callingUid, int callingPid, int callingUserId) {
 
         mHandler.post(() -> {
-            final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
-                    opPackageName);
-            final int modality = result.first;
-            final int error = result.second;
+            int modality = TYPE_NONE;
+            int result;
 
-            final boolean credentialAllowed = Utils.isDeviceCredentialAllowed(bundle);
-
-            if (error != BiometricConstants.BIOMETRIC_SUCCESS && credentialAllowed) {
-                // If there's a problem but device credential is allowed, only show credential UI.
-                bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
-                        Authenticator.TYPE_CREDENTIAL);
-            } else if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
-                // Check for errors, notify callback, and return
-                try {
-                    receiver.onError(modality, error, 0 /* vendorCode */);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to send error", e);
-                }
-                return;
+            try {
+                final Pair<Integer, Integer> pair = checkAndGetAuthenticators(userId, bundle,
+                        opPackageName);
+                modality = pair.first;
+                result = pair.second;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
+                result = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
             }
 
-            // Start preparing for authentication. Authentication starts when
-            // all modalities requested have invoked onReadyForAuthentication.
-            authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
-                    callingUid, callingPid, callingUserId, modality);
+            try {
+                if (result == BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL) {
+                    // If the app allowed device credential but the user hasn't set it up yet,
+                    // return this error.
+                    receiver.onError(modality, result, 0 /* vendorCode */);
+                } else if (result != BiometricConstants.BIOMETRIC_SUCCESS) {
+                    if (Utils.isDeviceCredentialAllowed(bundle)) {
+                        // If there's a problem with biometrics but device credential is allowed,
+                        // only show credential UI.
+                        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
+                                Authenticators.DEVICE_CREDENTIAL);
+                        authenticateInternal(token, sessionId, userId, receiver, opPackageName,
+                                bundle, callingUid, callingPid, callingUserId, modality);
+                    } else {
+                        receiver.onError(modality, result, 0 /* vendorCode */);
+                    }
+                } else {
+                    // BIOMETRIC_SUCCESS, proceed to authentication
+                    authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
+                            callingUid, callingPid, callingUserId, modality);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
+            }
         });
     }
 
@@ -1407,7 +1562,8 @@
         // with the cookie. Once all cookies are received, we can show the prompt
         // and let the services start authenticating. The cookie should be non-zero.
         final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
-        final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
+        final @Authenticators.Types int authenticators = bundle.getInt(
+                BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
         Slog.d(TAG, "Creating auth session. Modality: " + modality
                 + ", cookie: " + cookie
                 + ", authenticators: " + authenticators);
@@ -1415,7 +1571,7 @@
 
         // If it's only device credential, we don't need to wait - LockSettingsService is
         // always ready to check credential (SystemUI invokes that path).
-        if ((authenticators & ~Authenticator.TYPE_CREDENTIAL) != 0) {
+        if ((authenticators & ~Authenticators.DEVICE_CREDENTIAL) != 0) {
             modalities.put(modality, cookie);
         }
         mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
@@ -1423,7 +1579,7 @@
                 modality, requireConfirmation);
 
         try {
-            if (authenticators == Authenticator.TYPE_CREDENTIAL) {
+            if (authenticators == Authenticators.DEVICE_CREDENTIAL) {
                 mPendingAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
                 mCurrentAuthSession = mPendingAuthSession;
                 mPendingAuthSession = null;
@@ -1438,9 +1594,13 @@
             } else {
                 mPendingAuthSession.mState = STATE_AUTH_CALLED;
                 for (AuthenticatorWrapper authenticator : mAuthenticators) {
-                    authenticator.impl.prepareForAuthentication(requireConfirmation, token,
-                            sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid,
-                            callingPid, callingUserId);
+                    // TODO(b/141025588): use ids instead of modalities to avoid ambiguity.
+                    if (authenticator.modality == modality) {
+                        authenticator.impl.prepareForAuthentication(requireConfirmation, token,
+                                sessionId, userId, mInternalReceiver, opPackageName, cookie,
+                                callingUid, callingPid, callingUserId);
+                        break;
+                    }
                 }
             }
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 2de18c3..60f0e8e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics;
 
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
 
 import android.app.ActivityManager;
@@ -1013,8 +1014,13 @@
 
     private boolean isForegroundActivity(int uid, int pid) {
         try {
-            List<ActivityManager.RunningAppProcessInfo> procs =
+            final List<ActivityManager.RunningAppProcessInfo> procs =
                     ActivityManager.getService().getRunningAppProcesses();
+            if (procs == null) {
+                Slog.e(getTag(), "Processes null, defaulting to true");
+                return true;
+            }
+
             int N = procs.size();
             for (int i = 0; i < N; i++) {
                 ActivityManager.RunningAppProcessInfo proc = procs.get(i);
@@ -1206,6 +1212,11 @@
      * @return authenticator id for the calling user
      */
     protected long getAuthenticatorId(String opPackageName) {
+        if (isKeyguard(opPackageName)) {
+            // If an app tells us it's keyguard, check that it actually is.
+            checkPermission(USE_BIOMETRIC_INTERNAL);
+        }
+
         final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId());
         return mAuthenticatorIds.getOrDefault(userId, 0L);
     }
diff --git a/services/core/java/com/android/server/biometrics/BiometricStrengthController.java b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
new file mode 100644
index 0000000..4e16189
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricStrengthController.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class for maintaining and updating the strengths for biometric sensors. Strengths can only
+ * be downgraded from the device's default, and never upgraded.
+ */
+public class BiometricStrengthController implements DeviceConfig.OnPropertiesChangedListener {
+    private static final String TAG = "BiometricStrengthController";
+
+    private final BiometricService mService;
+
+    /**
+     * Flag stored in the DeviceConfig API: biometric modality strengths to downgrade.
+     * This is encoded as a key:value list, separated by comma, e.g.
+     *
+     * "id1:strength1,id2:strength2,id3:strength3"
+     *
+     * where strength is one of the values defined in
+     * {@link android.hardware.biometrics.Authenticators}
+     *
+     * Both id and strength should be int, otherwise Exception will be thrown when parsing and the
+     * downgrade will fail.
+     */
+    private static final String KEY_BIOMETRIC_STRENGTHS = "biometric_strengths";
+
+    /**
+     * Default (no-op) value of the flag KEY_BIOMETRIC_STRENGTHS
+     */
+    public static final String DEFAULT_BIOMETRIC_STRENGTHS = null;
+
+    BiometricStrengthController(@NonNull BiometricService service) {
+        mService = service;
+    }
+
+    void startListening() {
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BIOMETRICS,
+                BackgroundThread.getExecutor(), this);
+        updateStrengths();
+    }
+
+    @Override
+    public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+        for (String name : properties.getKeyset()) {
+            if (KEY_BIOMETRIC_STRENGTHS.equals(name)) {
+                updateStrengths();
+            }
+        }
+    }
+
+    /**
+     * Updates the strengths of authenticators in BiometricService if a matching ID's configuration
+     * has been changed.
+     */
+    private void updateStrengths() {
+        final Map<Integer, Integer> idToStrength = getIdToStrengthMap();
+        if (idToStrength == null) {
+            return;
+        }
+
+        for (BiometricService.AuthenticatorWrapper authenticator : mService.mAuthenticators) {
+            final int id = authenticator.id;
+            if (idToStrength.containsKey(id)) {
+                final int newStrength = idToStrength.get(id);
+                authenticator.updateStrength(newStrength);
+            }
+        }
+    }
+
+    /**
+     * @return a map of <ID, Strength>
+     */
+    private Map<Integer, Integer> getIdToStrengthMap() {
+        final String flags = DeviceConfig.getString(DeviceConfig.NAMESPACE_BIOMETRICS,
+                KEY_BIOMETRIC_STRENGTHS, DEFAULT_BIOMETRIC_STRENGTHS);
+        if (flags == null || flags.isEmpty()) {
+            Slog.d(TAG, "Flags are null or empty");
+            return null;
+        }
+
+        Map<Integer, Integer> map = new HashMap<>();
+        try {
+            for (String item : flags.split(",")) {
+                String[] elems = item.split(":");
+                final int id = Integer.parseInt(elems[0]);
+                final int strength = Integer.parseInt(elems[1]);
+                map.put(id, strength);
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Can't parse flag: " + flags);
+            map = null;
+        }
+        return map;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/OWNERS b/services/core/java/com/android/server/biometrics/OWNERS
new file mode 100644
index 0000000..8765c9a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+kchyn@google.com
+jaggies@google.com
+curtislb@google.com
+ilyamaty@google.com
+joshmccloskey@google.com
diff --git a/services/core/java/com/android/server/biometrics/SensorConfig.java b/services/core/java/com/android/server/biometrics/SensorConfig.java
new file mode 100644
index 0000000..9eda6da
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/SensorConfig.java
@@ -0,0 +1,33 @@
+/*
+ * 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.biometrics;
+
+/**
+ * Parsed sensor config. See core/res/res/values/config.xml config_biometric_sensors
+ */
+class SensorConfig {
+    final int mId;
+    final int mModality;
+    final int mStrength;
+
+    public SensorConfig(String config) {
+        String[] elems = config.split(":");
+        mId = Integer.parseInt(elems[0]);
+        mModality = Integer.parseInt(elems[1]);
+        mStrength = Integer.parseInt(elems[2]);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index ed5f9de..19f5358 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -16,15 +16,17 @@
 
 package com.android.server.biometrics;
 
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
 import android.content.Context;
-import android.hardware.biometrics.Authenticator;
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
-
-import com.android.internal.annotations.VisibleForTesting;
+import android.util.Slog;
 
 public class Utils {
     public static boolean isDebugEnabled(Context context, int targetUserId) {
@@ -45,41 +47,167 @@
     }
 
     /**
-     * Combine {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with
-     * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible
-     * enough.
+     * Combines {@link BiometricPrompt#KEY_ALLOW_DEVICE_CREDENTIAL} with
+     * {@link BiometricPrompt#KEY_AUTHENTICATORS_ALLOWED}, as the former is not flexible enough.
      */
     public static void combineAuthenticatorBundles(Bundle bundle) {
-        boolean biometricEnabled = true; // enabled by default
-        boolean credentialEnabled = bundle.getBoolean(
-                BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
-        if (bundle.get(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED) != null) {
-            final int authenticatorFlags =
-                    bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
-            biometricEnabled = (authenticatorFlags & Authenticator.TYPE_BIOMETRIC) != 0;
-            // Using both KEY_ALLOW_DEVICE_CREDENTIAL and KEY_AUTHENTICATORS_ALLOWED together
-            // is not supported. Default to overwriting.
-            credentialEnabled = (authenticatorFlags & Authenticator.TYPE_CREDENTIAL) != 0;
-        }
-
+        // Cache and remove explicit ALLOW_DEVICE_CREDENTIAL boolean flag from the bundle.
+        final boolean deviceCredentialAllowed =
+                bundle.getBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, false);
         bundle.remove(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL);
 
-        int authenticators = 0;
-        if (biometricEnabled) {
-            authenticators |= Authenticator.TYPE_BIOMETRIC;
+        final @Authenticators.Types int authenticators;
+        if (bundle.containsKey(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)) {
+            // Ignore ALLOW_DEVICE_CREDENTIAL flag if AUTH_TYPES_ALLOWED is defined.
+            authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, 0);
+        } else {
+            // Otherwise, use ALLOW_DEVICE_CREDENTIAL flag along with Weak+ biometrics by default.
+            authenticators = deviceCredentialAllowed
+                    ? Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK
+                    : Authenticators.BIOMETRIC_WEAK;
         }
-        if (credentialEnabled) {
-            authenticators |= Authenticator.TYPE_CREDENTIAL;
-        }
+
         bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
     }
 
     /**
+     * @param authenticators composed of one or more values from {@link Authenticators}
+     * @return true if device credential is allowed.
+     */
+    public static boolean isDeviceCredentialAllowed(@Authenticators.Types int authenticators) {
+        return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
+    }
+
+    /**
      * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
-     * @return true if device credential allowed.
+     * @return true if device credential is allowed.
      */
     public static boolean isDeviceCredentialAllowed(Bundle bundle) {
+        return isDeviceCredentialAllowed(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+    }
+
+    /**
+     * Checks if any of the publicly defined strengths are set.
+     *
+     * @param authenticators composed of one or more values from {@link Authenticators}
+     * @return minimal allowed biometric strength or 0 if biometric authentication is not allowed.
+     */
+    public static int getPublicBiometricStrength(@Authenticators.Types int authenticators) {
+        // Only biometrics WEAK and above are allowed to integrate with the public APIs.
+        return authenticators & Authenticators.BIOMETRIC_WEAK;
+    }
+
+    /**
+     * Checks if any of the publicly defined strengths are set.
+     *
+     * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
+     * @return minimal allowed biometric strength or 0 if biometric authentication is not allowed.
+     */
+    public static int getPublicBiometricStrength(Bundle bundle) {
+        return getPublicBiometricStrength(
+                bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+    }
+
+    /**
+     * Checks if any of the publicly defined strengths are set.
+     *
+     * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
+     * @return true if biometric authentication is allowed.
+     */
+    public static boolean isBiometricAllowed(Bundle bundle) {
+        return getPublicBiometricStrength(bundle) != 0;
+    }
+
+    /**
+     * @param sensorStrength the strength of the sensor
+     * @param requestedStrength the strength that it must meet
+     * @return true only if the sensor is at least as strong as the requested strength
+     */
+    public static boolean isAtLeastStrength(int sensorStrength, int requestedStrength) {
+        // If the authenticator contains bits outside of the requested strength, it is too weak.
+        return (~requestedStrength & sensorStrength) == 0;
+    }
+
+    /**
+     * Checks if the authenticator configuration is a valid combination of the public APIs
+     * @param bundle
+     * @return
+     */
+    public static boolean isValidAuthenticatorConfig(Bundle bundle) {
         final int authenticators = bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED);
-        return (authenticators & Authenticator.TYPE_CREDENTIAL) != 0;
+        return isValidAuthenticatorConfig(authenticators);
+    }
+
+    /**
+     * Checks if the authenticator configuration is a valid combination of the public APIs
+     * @param authenticators
+     * @return
+     */
+    public static boolean isValidAuthenticatorConfig(int authenticators) {
+        // The caller is not required to set the authenticators. But if they do, check the below.
+        if (authenticators == 0) {
+            return true;
+        }
+
+        // Check if any of the non-biometric and non-credential bits are set. If so, this is
+        // invalid.
+        final int testBits = ~(Authenticators.DEVICE_CREDENTIAL
+                | Authenticators.BIOMETRIC_MIN_STRENGTH);
+        if ((authenticators & testBits) != 0) {
+            Slog.e(BiometricService.TAG, "Non-biometric, non-credential bits found."
+                    + " Authenticators: " + authenticators);
+            return false;
+        }
+
+        // Check that biometrics bits are either NONE, WEAK, or STRONG. If NONE, DEVICE_CREDENTIAL
+        // should be set.
+        final int biometricBits = authenticators & Authenticators.BIOMETRIC_MIN_STRENGTH;
+        if (biometricBits == Authenticators.EMPTY_SET
+                && isDeviceCredentialAllowed(authenticators)) {
+            return true;
+        } else if (biometricBits == Authenticators.BIOMETRIC_STRONG) {
+            return true;
+        } else if (biometricBits == Authenticators.BIOMETRIC_WEAK) {
+            return true;
+        }
+
+        Slog.e(BiometricService.TAG, "Unsupported biometric flags. Authenticators: "
+                + authenticators);
+        // Non-supported biometric flags are being used
+        return false;
+    }
+
+    /**
+     * Converts error codes from BiometricConstants, which are used in most of the internal plumbing
+     * and eventually returned to {@link BiometricPrompt.AuthenticationCallback} to public
+     * {@link BiometricManager} constants, which are used by APIs such as
+     * {@link BiometricManager#canAuthenticate(int)}
+     *
+     * @param biometricConstantsCode see {@link BiometricConstants}
+     * @return see {@link BiometricManager}
+     */
+    public static int biometricConstantsToBiometricManager(int biometricConstantsCode) {
+        final int biometricManagerCode;
+
+        switch (biometricConstantsCode) {
+            case BiometricConstants.BIOMETRIC_SUCCESS:
+                biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
+                break;
+            case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
+            case BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL:
+                biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED;
+                break;
+            case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
+                biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+                break;
+            case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
+                biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
+                break;
+            default:
+                Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
+                biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+                break;
+        }
+        return biometricManagerCode;
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 9bae902..af8a366 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -39,11 +39,11 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.ISocketKeepaliveCallback;
+import android.net.InvalidPacketException;
 import android.net.KeepalivePacketData;
 import android.net.NattKeepalivePacketData;
 import android.net.NetworkAgent;
 import android.net.NetworkUtils;
-import android.net.SocketKeepalive.InvalidPacketException;
 import android.net.SocketKeepalive.InvalidSocketException;
 import android.net.TcpKeepalivePacketData;
 import android.net.util.IpUtils;
@@ -657,7 +657,10 @@
         final TcpKeepalivePacketData packet;
         try {
             packet = TcpKeepaliveController.getTcpKeepalivePacket(fd);
-        } catch (InvalidPacketException | InvalidSocketException e) {
+        } catch (InvalidSocketException e) {
+            notifyErrorCallback(cb, e.error);
+            return;
+        } catch (InvalidPacketException e) {
             notifyErrorCallback(cb, e.error);
             return;
         }
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
index e570ef1e..1129899 100644
--- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -30,8 +30,8 @@
 import static android.system.OsConstants.TIOCOUTQ;
 
 import android.annotation.NonNull;
+import android.net.InvalidPacketException;
 import android.net.NetworkUtils;
-import android.net.SocketKeepalive.InvalidPacketException;
 import android.net.SocketKeepalive.InvalidSocketException;
 import android.net.TcpKeepalivePacketData;
 import android.net.TcpKeepalivePacketDataParcelable;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 944a95d..44c8971 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -18,8 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InputMethodInfo;
 
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.server.LocalServices;
 
 import java.util.Collections;
@@ -57,6 +63,17 @@
     public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
 
     /**
+     * Called by the Autofill Frameworks to request an {@link InlineSuggestionsRequest} from
+     * the input method.
+     *
+     * @param componentName {@link ComponentName} of current app/activity.
+     * @param autofillId {@link AutofillId} of currently focused field.
+     * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object.
+     */
+    public abstract void onCreateInlineSuggestionsRequest(ComponentName componentName,
+            AutofillId autofillId, IInlineSuggestionsRequestCallback cb);
+
+    /**
      * Fake implementation of {@link InputMethodManagerInternal}.  All the methods do nothing.
      */
     private static final InputMethodManagerInternal NOP =
@@ -78,6 +95,17 @@
                 public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
                     return Collections.emptyList();
                 }
+
+                @Override
+                public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+                        AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+                    try {
+                        cb.onInlineSuggestionsUnsupported();
+                    } catch (RemoteException e) {
+                        Log.w("IMManagerInternal", "RemoteException calling"
+                                + " onInlineSuggestionsUnsupported: " + e);
+                    }
+                }
             };
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 471fa72..5865dc4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -109,6 +109,7 @@
 import android.view.Window;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
@@ -140,6 +141,7 @@
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethod;
 import com.android.internal.view.IInputMethodClient;
@@ -211,6 +213,8 @@
 
     static final int MSG_SYSTEM_UNLOCK_USER = 5000;
 
+    static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000;
+
     static final long TIME_TO_RECONNECT = 3 * 1000;
 
     static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
@@ -1785,6 +1789,16 @@
         return settings.getEnabledInputMethodListLocked();
     }
 
+    @GuardedBy("mMethodMap")
+    private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName,
+            AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+        if (mCurMethod != null) {
+            executeOrSendMessage(mCurMethod,
+                    mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod,
+                            componentName, autofillId, callback));
+        }
+    }
+
     /**
      * @param imiId if null, returns enabled subtypes for the current imi
      * @return enabled subtypes of the specified imi
@@ -3874,6 +3888,21 @@
                 final int userId = msg.arg1;
                 onUnlockUser(userId);
                 return true;
+
+            // ---------------------------------------------------------------
+            case MSG_INLINE_SUGGESTIONS_REQUEST:
+                args = (SomeArgs) msg.obj;
+                final ComponentName componentName = (ComponentName) args.arg2;
+                final AutofillId autofillId = (AutofillId) args.arg3;
+                final IInlineSuggestionsRequestCallback callback =
+                        (IInlineSuggestionsRequestCallback) args.arg4;
+                try {
+                    ((IInputMethod) args.arg1).onCreateInlineSuggestionsRequest(componentName,
+                            autofillId, callback);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e);
+                }
+                return true;
         }
         return false;
     }
@@ -4434,6 +4463,13 @@
         }
     }
 
+    private void onCreateInlineSuggestionsRequest(ComponentName componentName,
+            AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+        synchronized (mMethodMap) {
+            onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback);
+        }
+    }
+
     private static final class LocalServiceImpl extends InputMethodManagerInternal {
         @NonNull
         private final InputMethodManagerService mService;
@@ -4464,6 +4500,12 @@
         public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) {
             return mService.getEnabledInputMethodListAsUser(userId);
         }
+
+        @Override
+        public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+                AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+            mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+        }
     }
 
     @BinderThread
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 02e29e0..c13d55a 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -59,10 +59,12 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.InputChannel;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
 import android.view.inputmethod.InputMethodInfo;
@@ -82,6 +84,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodClient;
 import com.android.internal.view.IInputMethodManager;
@@ -187,6 +190,18 @@
                                 @UserIdInt int userId) {
                             return userIdToInputMethodInfoMapper.getAsList(userId);
                         }
+
+                        @Override
+                        public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+                                AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+                            try {
+                                //TODO(b/137800469): support multi client IMEs.
+                                cb.onInlineSuggestionsUnsupported();
+                            } catch (RemoteException e) {
+                                Log.w("MultiClientIMManager", "RemoteException calling"
+                                        + " onInlineSuggestionsUnsupported: " + e);
+                            }
+                        }
                     });
         }
 
diff --git a/services/core/java/com/android/server/integrity/OWNERS b/services/core/java/com/android/server/integrity/OWNERS
index 019aa4f..55a4e40 100644
--- a/services/core/java/com/android/server/integrity/OWNERS
+++ b/services/core/java/com/android/server/integrity/OWNERS
@@ -3,4 +3,3 @@
 mdchurchill@google.com
 sturla@google.com
 songpan@google.com
-bjy@google.com
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 9fcee50..e7b8860 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -183,8 +183,9 @@
     }
 
     public void setControlCategories(@NonNull IMediaRouter2Client client,
-            @Nullable List<String> categories) {
+            @NonNull List<String> categories) {
         Objects.requireNonNull(client, "client must not be null");
+        Objects.requireNonNull(categories, "categories must not be null");
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -390,8 +391,11 @@
 
     private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) {
         if (clientRecord != null) {
-            clientRecord.mControlCategories = categories;
+            if (clientRecord.mControlCategories.equals(categories)) {
+                return;
+            }
 
+            clientRecord.mControlCategories = categories;
             clientRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::updateClientUsage,
                             clientRecord.mUserRecord.mHandler, clientRecord));
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2cb7e3b..069aeef 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -47,7 +47,8 @@
 import android.media.Session2CommandGroup;
 import android.media.Session2Token;
 import android.media.session.IActiveSessionsListener;
-import android.media.session.ICallback;
+import android.media.session.IOnMediaKeyEventDispatchedListener;
+import android.media.session.IOnMediaKeyEventSessionChangedListener;
 import android.media.session.IOnMediaKeyListener;
 import android.media.session.IOnVolumeKeyLongPressListener;
 import android.media.session.ISession;
@@ -750,7 +751,10 @@
 
         private final int mFullUserId;
         private final MediaSessionStack mPriorityStack;
-        private final HashMap<IBinder, CallbackRecord> mCallbacks = new HashMap<>();
+        private final HashMap<IBinder, OnMediaKeyEventDispatchedListenerRecord>
+                mOnMediaKeyEventDispatchedListeners = new HashMap<>();
+        private final HashMap<IBinder, OnMediaKeyEventSessionChangedListenerRecord>
+                mOnMediaKeyEventSessionChangedListeners = new HashMap<>();
 
         private PendingIntent mLastMediaButtonReceiver;
         private ComponentName mRestoredMediaButtonReceiver;
@@ -796,21 +800,47 @@
             }
         }
 
-        public void registerCallbackLocked(ICallback callback, int uid) {
-            IBinder cbBinder = callback.asBinder();
-            CallbackRecord cr = new CallbackRecord(callback, uid);
-            mCallbacks.put(cbBinder, cr);
+        public void addOnMediaKeyEventDispatchedListenerLocked(
+                IOnMediaKeyEventDispatchedListener listener, int uid) {
+            IBinder cbBinder = listener.asBinder();
+            OnMediaKeyEventDispatchedListenerRecord cr =
+                    new OnMediaKeyEventDispatchedListenerRecord(listener, uid);
+            mOnMediaKeyEventDispatchedListeners.put(cbBinder, cr);
             try {
                 cbBinder.linkToDeath(cr, 0);
             } catch (RemoteException e) {
-                Log.w(TAG, "Failed to register callback", e);
-                mCallbacks.remove(cbBinder);
+                Log.w(TAG, "Failed to add listener", e);
+                mOnMediaKeyEventDispatchedListeners.remove(cbBinder);
             }
         }
 
-        public void unregisterCallbackLocked(ICallback callback) {
-            IBinder cbBinder = callback.asBinder();
-            CallbackRecord cr = mCallbacks.remove(cbBinder);
+        public void removeOnMediaKeyEventDispatchedListenerLocked(
+                IOnMediaKeyEventDispatchedListener listener) {
+            IBinder cbBinder = listener.asBinder();
+            OnMediaKeyEventDispatchedListenerRecord cr =
+                    mOnMediaKeyEventDispatchedListeners.remove(cbBinder);
+            cbBinder.unlinkToDeath(cr, 0);
+        }
+
+        public void addOnMediaKeyEventSessionChangedListenerLocked(
+                IOnMediaKeyEventSessionChangedListener listener, int uid) {
+            IBinder cbBinder = listener.asBinder();
+            OnMediaKeyEventSessionChangedListenerRecord cr =
+                    new OnMediaKeyEventSessionChangedListenerRecord(listener, uid);
+            mOnMediaKeyEventSessionChangedListeners.put(cbBinder, cr);
+            try {
+                cbBinder.linkToDeath(cr, 0);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to add listener", e);
+                mOnMediaKeyEventSessionChangedListeners.remove(cbBinder);
+            }
+        }
+
+        public void removeOnMediaKeyEventSessionChangedListener(
+                IOnMediaKeyEventSessionChangedListener listener) {
+            IBinder cbBinder = listener.asBinder();
+            OnMediaKeyEventSessionChangedListenerRecord cr =
+                    mOnMediaKeyEventSessionChangedListeners.remove(cbBinder);
             cbBinder.unlinkToDeath(cr, 0);
         }
 
@@ -832,8 +862,16 @@
             pw.println(indent + "Media key listener: " + mOnMediaKeyListener);
             pw.println(indent + "Media key listener package: "
                     + getCallingPackageName(mOnMediaKeyListenerUid));
-            pw.println(indent + "Callbacks: registered " + mCallbacks.size() + " callback(s)");
-            for (CallbackRecord cr : mCallbacks.values()) {
+            pw.println(indent + "OnMediaKeyEventDispatchedListener: added "
+                    + mOnMediaKeyEventDispatchedListeners.size() + " listener(s)");
+            for (OnMediaKeyEventDispatchedListenerRecord cr
+                    : mOnMediaKeyEventDispatchedListeners.values()) {
+                pw.println(indent + "  from " + getCallingPackageName(cr.uid));
+            }
+            pw.println(indent + "OnMediaKeyEventSessionChangedListener: added "
+                    + mOnMediaKeyEventSessionChangedListeners.size() + " listener(s)");
+            for (OnMediaKeyEventSessionChangedListenerRecord cr
+                    : mOnMediaKeyEventSessionChangedListeners.values()) {
                 pw.println(indent + "  from " + getCallingPackageName(cr.uid));
             }
             pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver);
@@ -895,19 +933,22 @@
                     mFullUserId);
         }
 
-        private void pushAddressedPlayerChangedLocked(ICallback callback) {
+        private void pushAddressedPlayerChangedLocked(
+                IOnMediaKeyEventSessionChangedListener callback) {
             try {
                 MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
                 if (mediaButtonSession != null) {
-                    callback.onAddressedPlayerChangedToMediaSession(
+                    callback.onMediaKeyEventSessionChanged(mediaButtonSession.getPackageName(),
                             mediaButtonSession.getSessionToken());
                 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
-                    callback.onAddressedPlayerChangedToMediaButtonReceiver(
+                    callback.onMediaKeyEventSessionChanged(
                             mCurrentFullUserRecord.mLastMediaButtonReceiver
-                                    .getIntent().getComponent());
+                                    .getIntent().getComponent().getPackageName(),
+                            null);
                 } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) {
-                    callback.onAddressedPlayerChangedToMediaButtonReceiver(
-                            mCurrentFullUserRecord.mRestoredMediaButtonReceiver);
+                    callback.onMediaKeyEventSessionChanged(
+                            mCurrentFullUserRecord.mRestoredMediaButtonReceiver.getPackageName(),
+                            null);
                 }
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e);
@@ -915,7 +956,8 @@
         }
 
         private void pushAddressedPlayerChangedLocked() {
-            for (CallbackRecord cr : mCallbacks.values()) {
+            for (OnMediaKeyEventSessionChangedListenerRecord cr
+                    : mOnMediaKeyEventSessionChangedListeners.values()) {
                 pushAddressedPlayerChangedLocked(cr.callback);
             }
         }
@@ -954,11 +996,12 @@
             return COMPONENT_TYPE_BROADCAST;
         }
 
-        final class CallbackRecord implements IBinder.DeathRecipient {
-            public final ICallback callback;
+        final class OnMediaKeyEventDispatchedListenerRecord implements IBinder.DeathRecipient {
+            public final IOnMediaKeyEventDispatchedListener callback;
             public final int uid;
 
-            CallbackRecord(ICallback callback, int uid) {
+            OnMediaKeyEventDispatchedListenerRecord(IOnMediaKeyEventDispatchedListener callback,
+                    int uid) {
                 this.callback = callback;
                 this.uid = uid;
             }
@@ -966,7 +1009,25 @@
             @Override
             public void binderDied() {
                 synchronized (mLock) {
-                    mCallbacks.remove(callback.asBinder());
+                    mOnMediaKeyEventDispatchedListeners.remove(callback.asBinder());
+                }
+            }
+        }
+
+        final class OnMediaKeyEventSessionChangedListenerRecord implements IBinder.DeathRecipient {
+            public final IOnMediaKeyEventSessionChangedListener callback;
+            public final int uid;
+
+            OnMediaKeyEventSessionChangedListenerRecord(
+                    IOnMediaKeyEventSessionChangedListener callback, int uid) {
+                this.callback = callback;
+                this.uid = uid;
+            }
+
+            @Override
+            public void binderDied() {
+                synchronized (mLock) {
+                    mOnMediaKeyEventSessionChangedListeners.remove(callback.asBinder());
                 }
             }
         }
@@ -1355,7 +1416,8 @@
         }
 
         @Override
-        public void registerCallback(final ICallback callback) {
+        public void addOnMediaKeyEventDispatchedListener(
+                final IOnMediaKeyEventDispatchedListener listener) {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final int userId = UserHandle.getUserId(uid);
@@ -1363,18 +1425,18 @@
             try {
                 if (!hasMediaControlPermission(pid, uid)) {
                     throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
-                            + "  register Callback");
+                            + "  add MediaKeyEventDispatchedListener");
                 }
                 synchronized (mLock) {
                     FullUserRecord user = getFullUserRecordLocked(userId);
                     if (user == null || user.mFullUserId != userId) {
-                        Log.w(TAG, "Only the full user can register the callback"
+                        Log.w(TAG, "Only the full user can add the listener"
                                 + ", userId=" + userId);
                         return;
                     }
-                    user.registerCallbackLocked(callback, uid);
-                    Log.d(TAG, "The callback (" + callback.asBinder()
-                            + ") is registered by " + getCallingPackageName(uid));
+                    user.addOnMediaKeyEventDispatchedListenerLocked(listener, uid);
+                    Log.d(TAG, "The MediaKeyEventDispatchedListener (" + listener.asBinder()
+                            + ") is added by " + getCallingPackageName(uid));
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -1382,7 +1444,8 @@
         }
 
         @Override
-        public void unregisterCallback(final ICallback callback) {
+        public void removeOnMediaKeyEventDispatchedListener(
+                final IOnMediaKeyEventDispatchedListener listener) {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
             final int userId = UserHandle.getUserId(uid);
@@ -1390,18 +1453,74 @@
             try {
                 if (!hasMediaControlPermission(pid, uid)) {
                     throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
-                            + "  unregister Callback");
+                            + "  remove MediaKeyEventDispatchedListener");
                 }
                 synchronized (mLock) {
                     FullUserRecord user = getFullUserRecordLocked(userId);
                     if (user == null || user.mFullUserId != userId) {
-                        Log.w(TAG, "Only the full user can unregister the callback"
+                        Log.w(TAG, "Only the full user can remove the listener"
                                 + ", userId=" + userId);
                         return;
                     }
-                    user.unregisterCallbackLocked(callback);
-                    Log.d(TAG, "The callback (" + callback.asBinder()
-                            + ") is unregistered by " + getCallingPackageName(uid));
+                    user.removeOnMediaKeyEventDispatchedListenerLocked(listener);
+                    Log.d(TAG, "The MediaKeyEventDispatchedListener (" + listener.asBinder()
+                            + ") is removed by " + getCallingPackageName(uid));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void addOnMediaKeyEventSessionChangedListener(
+                final IOnMediaKeyEventSessionChangedListener listener) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(uid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!hasMediaControlPermission(pid, uid)) {
+                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+                            + "  add MediaKeyEventSessionChangedListener");
+                }
+                synchronized (mLock) {
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    if (user == null || user.mFullUserId != userId) {
+                        Log.w(TAG, "Only the full user can add the listener"
+                                + ", userId=" + userId);
+                        return;
+                    }
+                    user.addOnMediaKeyEventSessionChangedListenerLocked(listener, uid);
+                    Log.d(TAG, "The MediaKeyEventSessionChangedListener (" + listener.asBinder()
+                            + ") is added by " + getCallingPackageName(uid));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void removeOnMediaKeyEventSessionChangedListener(
+                final IOnMediaKeyEventSessionChangedListener listener) {
+            final int pid = Binder.getCallingPid();
+            final int uid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(uid);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (!hasMediaControlPermission(pid, uid)) {
+                    throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+                            + "  remove MediaKeyEventSessionChangedListener");
+                }
+                synchronized (mLock) {
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    if (user == null || user.mFullUserId != userId) {
+                        Log.w(TAG, "Only the full user can remove the listener"
+                                + ", userId=" + userId);
+                        return;
+                    }
+                    user.removeOnMediaKeyEventSessionChangedListener(listener);
+                    Log.d(TAG, "The MediaKeyEventSessionChangedListener (" + listener.asBinder()
+                            + ") is removed by " + getCallingPackageName(uid));
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -2015,10 +2134,10 @@
                         needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
                         mKeyEventReceiver);
                 try {
-                    for (FullUserRecord.CallbackRecord cr
-                            : mCurrentFullUserRecord.mCallbacks.values()) {
-                        cr.callback.onMediaKeyEventDispatchedToMediaSession(
-                                keyEvent, session.getSessionToken());
+                    for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+                            : mCurrentFullUserRecord.mOnMediaKeyEventDispatchedListeners.values()) {
+                        cr.callback.onMediaKeyEventDispatched(
+                                keyEvent, session.getPackageName(), session.getSessionToken());
                     }
                 } catch (RemoteException e) {
                     Log.w(TAG, "Failed to send callback", e);
@@ -2048,10 +2167,11 @@
                         ComponentName componentName = mCurrentFullUserRecord
                                 .mLastMediaButtonReceiver.getIntent().getComponent();
                         if (componentName != null) {
-                            for (FullUserRecord.CallbackRecord cr
-                                    : mCurrentFullUserRecord.mCallbacks.values()) {
-                                cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
-                                                keyEvent, componentName);
+                            for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+                                    : mCurrentFullUserRecord
+                                    .mOnMediaKeyEventDispatchedListeners.values()) {
+                                cr.callback.onMediaKeyEventDispatched(keyEvent,
+                                        componentName.getPackageName(), null);
                             }
                         }
                     } else {
@@ -2083,10 +2203,11 @@
                             Log.w(TAG, "Error sending media button to the restored intent "
                                     + receiver + ", type=" + componentType, e);
                         }
-                        for (FullUserRecord.CallbackRecord cr
-                                : mCurrentFullUserRecord.mCallbacks.values()) {
-                            cr.callback.onMediaKeyEventDispatchedToMediaButtonReceiver(
-                                            keyEvent, receiver);
+                        for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr
+                                : mCurrentFullUserRecord
+                                .mOnMediaKeyEventDispatchedListeners.values()) {
+                            cr.callback.onMediaKeyEventDispatched(keyEvent,
+                                    receiver.getPackageName(), null);
                         }
                     }
                 } catch (CanceledException e) {
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 7098435..a7e40cb 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -59,6 +59,12 @@
             return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
         }
 
+        // If a score has been assigned by notification assistant service, use this service
+        // rank results within each bucket instead of this comparator implementation.
+        if (left.getRankingScore() != right.getRankingScore()) {
+            return -1 * Float.compare(left.getRankingScore(), right.getRankingScore());
+        }
+
         // first all colorized notifications
         boolean leftImportantColorized = isImportantColorized(left);
         boolean rightImportantColorized = isImportantColorized(right);
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index e968fb70..c8afcc9 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -144,6 +144,7 @@
     private int mSystemImportance = IMPORTANCE_UNSPECIFIED;
     private int mAssistantImportance = IMPORTANCE_UNSPECIFIED;
     private int mImportance = IMPORTANCE_UNSPECIFIED;
+    private float mRankingScore = 0f;
     // Field used in global sort key to bypass normal notifications
     private int mCriticality = CriticalNotificationExtractor.NORMAL;
     // A MetricsEvent.NotificationImportanceExplanation, tracking source of mImportance.
@@ -655,6 +656,9 @@
                     importance = Math.min(IMPORTANCE_HIGH, importance);
                     setAssistantImportance(importance);
                 }
+                if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
+                    mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
+                }
                 if (!signals.isEmpty() && adjustment.getIssuer() != null) {
                     mAdjustmentIssuer = adjustment.getIssuer();
                 }
@@ -772,6 +776,10 @@
         return mImportance;
     }
 
+    public float getRankingScore() {
+        return mRankingScore;
+    }
+
     public CharSequence getImportanceExplanation() {
         switch (mImportanceExplanationCode) {
             case MetricsEvent.IMPORTANCE_EXPLANATION_UNKNOWN:
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index e055116..ac3bf9a 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -26,6 +26,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -44,6 +45,38 @@
 
     private final VerifyCallback mVerifyCallback;
 
+    /**
+     * @return nullable actor result with {@link ActorState} failure status
+     */
+    static Pair<String, ActorState> getPackageNameForActor(String actorUriString,
+            Map<String, Map<String, String>> namedActors) {
+        if (namedActors.isEmpty()) {
+            return Pair.create(null, ActorState.NO_NAMED_ACTORS);
+        }
+
+        Uri actorUri = Uri.parse(actorUriString);
+
+        String actorScheme = actorUri.getScheme();
+        List<String> actorPathSegments = actorUri.getPathSegments();
+        if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
+            return Pair.create(null, ActorState.INVALID_OVERLAYABLE_ACTOR_NAME);
+        }
+
+        String actorNamespace = actorUri.getAuthority();
+        Map<String, String> namespace = namedActors.get(actorNamespace);
+        if (namespace == null) {
+            return Pair.create(null, ActorState.MISSING_NAMESPACE);
+        }
+
+        String actorName = actorPathSegments.get(0);
+        String packageName = namespace.get(actorName);
+        if (TextUtils.isEmpty(packageName)) {
+            return Pair.create(null, ActorState.MISSING_ACTOR_NAME);
+        }
+
+        return Pair.create(packageName, ActorState.ALLOWED);
+    }
+
     public OverlayActorEnforcer(@NonNull VerifyCallback verifyCallback) {
         mVerifyCallback = verifyCallback;
     }
@@ -141,31 +174,14 @@
             }
         }
 
-        Map<String, ? extends Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
-        if (namedActors.isEmpty()) {
-            return ActorState.NO_NAMED_ACTORS;
+        Map<String, Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
+        Pair<String, ActorState> actorUriPair = getPackageNameForActor(actor, namedActors);
+        ActorState actorUriState = actorUriPair.second;
+        if (actorUriState != ActorState.ALLOWED) {
+            return actorUriState;
         }
 
-        Uri actorUri = Uri.parse(actor);
-
-        String actorScheme = actorUri.getScheme();
-        List<String> actorPathSegments = actorUri.getPathSegments();
-        if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
-            return ActorState.INVALID_OVERLAYABLE_ACTOR_NAME;
-        }
-
-        String actorNamespace = actorUri.getAuthority();
-        Map<String, String> namespace = namedActors.get(actorNamespace);
-        if (namespace == null) {
-            return ActorState.MISSING_NAMESPACE;
-        }
-
-        String actorName = actorPathSegments.get(0);
-        String packageName = namespace.get(actorName);
-        if (TextUtils.isEmpty(packageName)) {
-            return ActorState.MISSING_ACTOR_NAME;
-        }
-
+        String packageName = actorUriPair.first;
         PackageInfo packageInfo = mVerifyCallback.getPackageInfo(packageName, userId);
         if (packageInfo == null) {
             return ActorState.MISSING_APP_INFO;
@@ -192,7 +208,7 @@
      * For easier logging/debugging, a set of all possible failure/success states when running
      * enforcement.
      */
-    private enum ActorState {
+    enum ActorState {
         ALLOWED,
         INVALID_ACTOR,
         MISSING_NAMESPACE,
@@ -244,7 +260,7 @@
          * value maps actor name to package name
          */
         @NonNull
-        Map<String, ? extends Map<String, String>> getNamedActors();
+        Map<String, Map<String, String>> getNamedActors();
 
         /**
          * @return true if the target package has declared an overlayable
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 8b69946..f1947ac 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1073,8 +1073,6 @@
             mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         }
 
-        // TODO(b/143096091): Remove PackageInfo cache so that PackageManager is always queried
-        //  to enforce visibility/other permission checks
         public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
                 final boolean useCache) {
             if (useCache) {
@@ -1097,18 +1095,12 @@
 
         @Override
         public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
-            // TODO(b/143096091): Remove clearing calling ID
-            long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                return getPackageInfo(packageName, userId, true);
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
-            }
+            return getPackageInfo(packageName, userId, true);
         }
 
         @NonNull
         @Override
-        public Map<String, ? extends Map<String, String>> getNamedActors() {
+        public Map<String, Map<String, String>> getNamedActors() {
             return SystemConfig.getInstance().getNamedActors();
         }
 
@@ -1136,57 +1128,45 @@
         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
                 @Nullable String targetOverlayableName, int userId)
                 throws IOException {
-            // TODO(b/143096091): Remove clearing calling ID
-            long callingIdentity = Binder.clearCallingIdentity();
+            PackageInfo packageInfo = getPackageInfo(packageName, userId);
+            if (packageInfo == null) {
+                throw new IOException("Unable to get target package");
+            }
+
+            String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+
+            ApkAssets apkAssets = null;
             try {
-                PackageInfo packageInfo = getPackageInfo(packageName, userId);
-                if (packageInfo == null) {
-                    throw new IOException("Unable to get target package");
-                }
-
-                String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
-
-                ApkAssets apkAssets = null;
-                try {
-                    apkAssets = ApkAssets.loadFromPath(baseCodePath);
-                    return apkAssets.getOverlayableInfo(targetOverlayableName);
-                } finally {
-                    if (apkAssets != null) {
-                        try {
-                            apkAssets.close();
-                        } catch (Throwable ignored) {
-                        }
+                apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                return apkAssets.getOverlayableInfo(targetOverlayableName);
+            } finally {
+                if (apkAssets != null) {
+                    try {
+                        apkAssets.close();
+                    } catch (Throwable ignored) {
                     }
                 }
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
             }
         }
 
         @Override
         public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
                 throws RemoteException, IOException {
-            // TODO(b/143096091): Remove clearing calling ID
-            long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
-                        userId);
-                String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+            PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
+                    userId);
+            String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
 
-                ApkAssets apkAssets = null;
-                try {
-                    apkAssets = ApkAssets.loadFromPath(baseCodePath);
-                    return apkAssets.definesOverlayable();
-                } finally {
-                    if (apkAssets != null) {
-                        try {
-                            apkAssets.close();
-                        } catch (Throwable ignored) {
-                        }
+            ApkAssets apkAssets = null;
+            try {
+                apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                return apkAssets.definesOverlayable();
+            } finally {
+                if (apkAssets != null) {
+                    try {
+                        apkAssets.close();
+                    } catch (Throwable ignored) {
                     }
                 }
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
             }
         }
 
@@ -1229,16 +1209,10 @@
         @Nullable
         @Override
         public String[] getPackagesForUid(int uid) {
-            // TODO(b/143096091): Remove clearing calling ID
-            long callingIdentity = Binder.clearCallingIdentity();
             try {
-                try {
-                    return mPackageManager.getPackagesForUid(uid);
-                } catch (RemoteException ignored) {
-                    return null;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
+                return mPackageManager.getPackagesForUid(uid);
+            } catch (RemoteException ignored) {
+                return null;
             }
         }
 
diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
new file mode 100644
index 0000000..8bea119
--- /dev/null
+++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
@@ -0,0 +1,375 @@
+/*
+ * 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.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.parsing.AndroidPackage;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.PackageSetting;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Track visibility of a targets and overlays to actors.
+ *
+ * 4 cases to handle:
+ * <ol>
+ *     <li>Target adds/changes an overlayable to add a reference to an actor
+ *         <ul>
+ *             <li>Must expose target to actor</li>
+ *             <li>Must expose any overlays that pointed to that overlayable name to the actor</li>
+ *         </ul>
+ *     </li>
+ *     <li>Target removes/changes an overlayable to remove a reference to an actor
+ *         <ul>
+ *             <li>If this target has no other overlayables referencing the actor, hide the
+ *             target</li>
+ *             <li>For all overlays targeting this overlayable, if the overlay is only visible to
+ *             the actor through this overlayable, hide the overlay</li>
+ *         </ul>
+ *     </li>
+ *     <li>Overlay adds/changes an overlay tag to add a reference to an overlayable name
+ *         <ul>
+ *             <li>Expose this overlay to the actor defined by the target overlayable</li>
+ *         </ul>
+ *     </li>
+ *     <li>Overlay removes/changes an overlay tag to remove a reference to an overlayable name
+ *         <ul>
+ *             <li>If this overlay is only visible to an actor through this overlayable name's
+ *             target's actor</li>
+ *         </ul>
+ *     </li>
+ * </ol>
+ *
+ * In this class, the names "actor", "target", and "overlay" all refer to the ID representations.
+ * All other use cases are named appropriate. "actor" is actor name, "target" is target package
+ * name, and "overlay" is overlay package name.
+ */
+public class OverlayReferenceMapper {
+
+    private final Object mLock = new Object();
+
+    /**
+     * Keys are actors, values are maps which map target to a set of overlays targeting it.
+     * The presence of a target in the value map means the actor and targets are connected, even
+     * if the corresponding target's set is empty.
+     * See class comment for specific types.
+     */
+    @GuardedBy("mLock")
+    private final Map<String, Map<String, Set<String>>> mActorToTargetToOverlays = new HashMap<>();
+
+    /**
+     * Keys are actor package names, values are generic package names the actor should be able
+     * to see.
+     */
+    @GuardedBy("mLock")
+    private final Map<String, Set<String>> mActorPkgToPkgs = new HashMap<>();
+
+    @GuardedBy("mLock")
+    private boolean mDeferRebuild;
+
+    @NonNull
+    private final Provider mProvider;
+
+    /**
+     * @param deferRebuild whether or not to defer rebuild calls on add/remove until first get call;
+     *                     useful during boot when multiple packages are added in rapid succession
+     *                     and queries in-between are not expected
+     */
+    public OverlayReferenceMapper(boolean deferRebuild, @Nullable Provider provider) {
+        this.mDeferRebuild = deferRebuild;
+        this.mProvider = provider != null ? provider : new Provider() {
+            @Nullable
+            @Override
+            public String getActorPkg(String actor) {
+                Map<String, Map<String, String>> namedActors = SystemConfig.getInstance()
+                        .getNamedActors();
+
+                Pair<String, OverlayActorEnforcer.ActorState> actorPair =
+                        OverlayActorEnforcer.getPackageNameForActor(actor, namedActors);
+                return actorPair.first;
+            }
+
+            @NonNull
+            @Override
+            public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) {
+                String target = pkg.getOverlayTarget();
+                if (TextUtils.isEmpty(target)) {
+                    return Collections.emptyMap();
+                }
+
+                String overlayable = pkg.getOverlayTargetName();
+                Map<String, Set<String>> targetToOverlayables = new HashMap<>();
+                Set<String> overlayables = new HashSet<>();
+                overlayables.add(overlayable);
+                targetToOverlayables.put(target, overlayables);
+                return targetToOverlayables;
+            }
+        };
+    }
+
+    /**
+     * @return mapping of actor package to a set of packages it can view
+     */
+    @NonNull
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public Map<String, Set<String>> getActorPkgToPkgs() {
+        return mActorPkgToPkgs;
+    }
+
+    public boolean isValidActor(@NonNull String targetName, @NonNull String actorPackageName) {
+        synchronized (mLock) {
+            assertMapBuilt();
+            Set<String> validSet = mActorPkgToPkgs.get(actorPackageName);
+            return validSet != null && validSet.contains(targetName);
+        }
+    }
+
+    /**
+     * Add a package to be considered for visibility. Currently supports adding as a target and/or
+     * an overlay. Adding an actor is not supported. Those are configured as part of
+     * {@link SystemConfig#getNamedActors()}.
+     *
+     * @param pkg the package to add
+     * @param otherPkgs map of other packages to consider, excluding {@param pkg}
+     */
+    public void addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
+        synchronized (mLock) {
+            if (!pkg.getOverlayables().isEmpty()) {
+                addTarget(pkg, otherPkgs);
+            }
+
+            // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
+            if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
+                addOverlay(pkg, otherPkgs);
+            }
+
+            if (!mDeferRebuild) {
+                rebuild();
+            }
+        }
+    }
+
+    /**
+     * Removes a package to be considered for visibility. Currently supports removing as a target
+     * and/or an overlay. Removing an actor is not supported. Those are staticly configured as part
+     * of {@link SystemConfig#getNamedActors()}.
+     *
+     * @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)}
+     */
+    public void removePkg(String pkgName) {
+        synchronized (mLock) {
+            removeTarget(pkgName);
+            removeOverlay(pkgName);
+
+            if (!mDeferRebuild) {
+                rebuild();
+            }
+        }
+    }
+
+    private void removeTarget(String target) {
+        synchronized (mLock) {
+            Iterator<Map<String, Set<String>>> iterator =
+                    mActorToTargetToOverlays.values().iterator();
+            while (iterator.hasNext()) {
+                Map<String, Set<String>> next = iterator.next();
+                next.remove(target);
+                if (next.isEmpty()) {
+                    iterator.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Associate an actor with an association of a new target to overlays for that target.
+     *
+     * If a target overlays itself, it will not be associated with itself, as only one half of the
+     * relationship needs to exist for visibility purposes.
+     */
+    private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs) {
+        synchronized (mLock) {
+            String target = targetPkg.getPackageName();
+            removeTarget(target);
+
+            Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
+            for (String overlayable : overlayablesToActors.keySet()) {
+                String actor = overlayablesToActors.get(overlayable);
+                addTargetToMap(actor, target);
+
+                for (AndroidPackage overlayPkg : otherPkgs.values()) {
+                    Map<String, Set<String>> targetToOverlayables =
+                            mProvider.getTargetToOverlayables(overlayPkg);
+                    Set<String> overlayables = targetToOverlayables.get(target);
+                    if (CollectionUtils.isEmpty(overlayables)) {
+                        continue;
+                    }
+
+                    if (overlayables.contains(overlayable)) {
+                        addOverlayToMap(actor, target, overlayPkg.getPackageName());
+                    }
+                }
+            }
+        }
+    }
+
+    private void removeOverlay(String overlay) {
+        synchronized (mLock) {
+            for (Map<String, Set<String>> targetToOverlays : mActorToTargetToOverlays.values()) {
+                for (Set<String> overlays : targetToOverlays.values()) {
+                    overlays.remove(overlay);
+                }
+            }
+        }
+    }
+
+    /**
+     * Associate an actor with an association of targets to overlays for a new overlay.
+     *
+     * If an overlay targets itself, it will not be associated with itself, as only one half of the
+     * relationship needs to exist for visibility purposes.
+     */
+    private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs) {
+        synchronized (mLock) {
+            String overlay = overlayPkg.getPackageName();
+            removeOverlay(overlay);
+
+            Map<String, Set<String>> targetToOverlayables =
+                    mProvider.getTargetToOverlayables(overlayPkg);
+            for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) {
+                String target = entry.getKey();
+                Set<String> overlayables = entry.getValue();
+                AndroidPackage targetPkg = otherPkgs.get(target);
+                if (targetPkg == null) {
+                    continue;
+                }
+
+                String targetPkgName = targetPkg.getPackageName();
+                Map<String, String> overlayableToActor = targetPkg.getOverlayables();
+                for (String overlayable : overlayables) {
+                    String actor = overlayableToActor.get(overlayable);
+                    if (TextUtils.isEmpty(actor)) {
+                        continue;
+                    }
+                    addOverlayToMap(actor, targetPkgName, overlay);
+                }
+            }
+        }
+    }
+
+    public void rebuildIfDeferred() {
+        synchronized (mLock) {
+            if (mDeferRebuild) {
+                rebuild();
+                mDeferRebuild = false;
+            }
+        }
+    }
+
+    private void assertMapBuilt() {
+        if (mDeferRebuild) {
+            throw new IllegalStateException("The actor map must be built by calling "
+                    + "rebuildIfDeferred before it is queried");
+        }
+    }
+
+    private void rebuild() {
+        synchronized (mLock) {
+            mActorPkgToPkgs.clear();
+            for (String actor : mActorToTargetToOverlays.keySet()) {
+                String actorPkg = mProvider.getActorPkg(actor);
+                if (TextUtils.isEmpty(actorPkg)) {
+                    continue;
+                }
+
+                Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+                Set<String> pkgs = new HashSet<>();
+
+                for (String target : targetToOverlays.keySet()) {
+                    Set<String> overlays = targetToOverlays.get(target);
+                    pkgs.add(target);
+                    pkgs.addAll(overlays);
+                }
+
+                mActorPkgToPkgs.put(actorPkg, pkgs);
+            }
+        }
+    }
+
+    private void addTargetToMap(String actor, String target) {
+        Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+        if (targetToOverlays == null) {
+            targetToOverlays = new HashMap<>();
+            mActorToTargetToOverlays.put(actor, targetToOverlays);
+        }
+
+        Set<String> overlays = targetToOverlays.get(target);
+        if (overlays == null) {
+            overlays = new HashSet<>();
+            targetToOverlays.put(target, overlays);
+        }
+    }
+
+    private void addOverlayToMap(String actor, String target, String overlay) {
+        synchronized (mLock) {
+            Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+            if (targetToOverlays == null) {
+                targetToOverlays = new HashMap<>();
+                mActorToTargetToOverlays.put(actor, targetToOverlays);
+            }
+
+            Set<String> overlays = targetToOverlays.get(target);
+            if (overlays == null) {
+                overlays = new HashSet<>();
+                targetToOverlays.put(target, overlays);
+            }
+
+            overlays.add(overlay);
+        }
+    }
+
+    public interface Provider {
+
+        /**
+         * Given the actor string from an overlayable definition, return the actor's package name.
+         */
+        @Nullable
+        String getActorPkg(String actor);
+
+        /**
+         * Mock response of multiple overlay tags.
+         *
+         * TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests
+         */
+        @NonNull
+        Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 8374ee6..c4bcf80 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -16,8 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.content.pm.PackageParser.Component;
-import static android.content.pm.PackageParser.IntentInfo;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
 
@@ -26,7 +24,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.parsing.AndroidPackage;
 import android.content.pm.parsing.ComponentParseUtils;
 import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
@@ -50,9 +47,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.FgThread;
+import com.android.server.om.OverlayReferenceMapper;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -109,11 +106,16 @@
 
     private final FeatureConfig mFeatureConfig;
 
+    private final OverlayReferenceMapper mOverlayReferenceMapper;
+
     AppsFilter(FeatureConfig featureConfig, String[] forceQueryableWhitelist,
-            boolean systemAppsQueryable) {
+            boolean systemAppsQueryable,
+            @Nullable OverlayReferenceMapper.Provider overlayProvider) {
         mFeatureConfig = featureConfig;
         mForceQueryableByDevicePackageNames = forceQueryableWhitelist;
         mSystemAppsQueryable = systemAppsQueryable;
+        mOverlayReferenceMapper = new OverlayReferenceMapper(true /*deferRebuild*/,
+                overlayProvider);
     }
 
     public interface FeatureConfig {
@@ -193,7 +195,7 @@
             }
         }
         return new AppsFilter(featureConfig, forcedQueryablePackageNames,
-                forceSystemAppsQueryable);
+                forceSystemAppsQueryable, null);
     }
 
     /** Returns true if the querying package may query for the potential target package */
@@ -282,6 +284,7 @@
 
     public void onSystemReady() {
         mFeatureConfig.onSystemReady();
+        mOverlayReferenceMapper.rebuildIfDeferred();
     }
 
     /**
@@ -338,6 +341,16 @@
                     }
                 }
             }
+
+            int existingSize = existingSettings.size();
+            ArrayMap<String, AndroidPackage> existingPkgs = new ArrayMap<>(existingSize);
+            for (int index = 0; index < existingSize; index++) {
+                PackageSetting pkgSetting = existingSettings.valueAt(index);
+                if (pkgSetting.pkg != null) {
+                    existingPkgs.put(pkgSetting.name, pkgSetting.pkg);
+                }
+            }
+            mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -381,6 +394,8 @@
                 addPackage(setting.sharedUser.packages.valueAt(i), existingSettings);
             }
         }
+
+        mOverlayReferenceMapper.removePkg(setting.name);
     }
 
     /**
@@ -397,8 +412,7 @@
             PackageSetting targetPkgSetting, int userId) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplication");
         try {
-            if (!shouldFilterApplicationInternal(callingUid, callingSetting,
-                    targetPkgSetting,
+            if (!shouldFilterApplicationInternal(callingUid, callingSetting, targetPkgSetting,
                     userId)) {
                 return false;
             }
@@ -412,8 +426,8 @@
         }
     }
 
-    private boolean shouldFilterApplicationInternal(int callingUid,
-            SettingBase callingSetting, PackageSetting targetPkgSetting, int userId) {
+    private boolean shouldFilterApplicationInternal(int callingUid, SettingBase callingSetting,
+            PackageSetting targetPkgSetting, int userId) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "shouldFilterApplicationInternal");
         try {
             final boolean featureEnabled = mFeatureConfig.isGloballyEnabled();
@@ -530,6 +544,29 @@
                     }
                 }
             }
+
+            if (callingSharedPkgSettings != null) {
+                int size = callingSharedPkgSettings.size();
+                for (int index = 0; index < size; index++) {
+                    PackageSetting pkgSetting = callingSharedPkgSettings.valueAt(index);
+                    if (mOverlayReferenceMapper.isValidActor(targetName, pkgSetting.name)) {
+                        if (DEBUG_LOGGING) {
+                            log(callingPkgSetting, targetPkgSetting,
+                                    "matches shared user of package that acts on target of "
+                                            + "overlay");
+                        }
+                        return false;
+                    }
+                }
+            } else {
+                if (mOverlayReferenceMapper.isValidActor(targetName, callingPkgSetting.name)) {
+                    if (DEBUG_LOGGING) {
+                        log(callingPkgSetting, targetPkgSetting, "acts on target of overlay");
+                    }
+                    return false;
+                }
+            }
+
             return true;
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 30b2c9d..8333ae5 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -39,6 +39,7 @@
 import android.content.pm.AuxiliaryResolveInfo;
 import android.content.pm.InstantAppIntentFilter;
 import android.content.pm.InstantAppRequest;
+import android.content.pm.InstantAppRequestInfo;
 import android.content.pm.InstantAppResolveInfo;
 import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
 import android.metrics.LogMaker;
@@ -47,6 +48,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 
@@ -63,7 +66,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
-import java.util.UUID;
 
 /** @hide */
 public abstract class InstantAppResolver {
@@ -117,26 +119,40 @@
         return sanitizedIntent;
     }
 
+    /**
+     * Generate an {@link InstantAppDigest} from an {@link Intent} which contains hashes of the
+     * host. The object contains both secure and insecure hash array variants, and the secure
+     * version must be passed along to ensure the random data is consistent.
+     */
+    @NonNull
+    public static InstantAppDigest parseDigest(@NonNull Intent origIntent) {
+        if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) {
+            return new InstantAppResolveInfo.InstantAppDigest(origIntent.getData().getHost(),
+                    5 /*maxDigests*/);
+        } else {
+            return InstantAppResolveInfo.InstantAppDigest.UNDEFINED;
+        }
+    }
+
     public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(
             InstantAppResolverConnection connection, InstantAppRequest requestObj) {
         final long startTime = System.currentTimeMillis();
-        final String token = UUID.randomUUID().toString();
+        final String token = requestObj.token;
         if (DEBUG_INSTANT) {
             Log.d(TAG, "[" + token + "] Phase1; resolving");
         }
-        final Intent origIntent = requestObj.origIntent;
-        final Intent sanitizedIntent = sanitizeIntent(origIntent);
 
         AuxiliaryResolveInfo resolveInfo = null;
         @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
+        Intent origIntent = requestObj.origIntent;
         try {
             final List<InstantAppResolveInfo> instantAppResolveInfoList =
-                    connection.getInstantAppResolveInfoList(sanitizedIntent,
-                            requestObj.digest.getDigestPrefixSecure(), requestObj.userId, token);
+                    connection.getInstantAppResolveInfoList(buildRequestInfo(requestObj));
             if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
                 resolveInfo = InstantAppResolver.filterInstantAppIntent(
                         instantAppResolveInfoList, origIntent, requestObj.resolvedType,
-                        requestObj.userId, origIntent.getPackage(), requestObj.digest, token);
+                        requestObj.userId, origIntent.getPackage(), token,
+                        requestObj.hostDigestPrefixSecure);
             }
         } catch (ConnectionException e) {
             if (e.failure == ConnectionException.FAILURE_BIND) {
@@ -166,7 +182,7 @@
         // if the match external flag is set, return an empty resolve info instead of a null result.
         if (resolveInfo == null && (origIntent.getFlags() & FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
             return new AuxiliaryResolveInfo(token, false, createFailureIntent(origIntent, token),
-                    null /* filters */);
+                    null /* filters */, requestObj.hostDigestPrefixSecure);
         }
         return resolveInfo;
     }
@@ -175,7 +191,7 @@
             InstantAppResolverConnection connection, InstantAppRequest requestObj,
             ActivityInfo instantAppInstaller, Handler callbackHandler) {
         final long startTime = System.currentTimeMillis();
-        final String token = requestObj.responseObj.token;
+        final String token = requestObj.token;
         if (DEBUG_INSTANT) {
             Log.d(TAG, "[" + token + "] Phase2; resolving");
         }
@@ -191,8 +207,8 @@
                     final AuxiliaryResolveInfo instantAppIntentInfo =
                             InstantAppResolver.filterInstantAppIntent(
                                     instantAppResolveInfoList, origIntent, null /*resolvedType*/,
-                                    0 /*userId*/, origIntent.getPackage(), requestObj.digest,
-                                    token);
+                                    0 /*userId*/, origIntent.getPackage(),
+                                    token, requestObj.hostDigestPrefixSecure);
                     if (instantAppIntentInfo != null) {
                         failureIntent = instantAppIntentInfo.failureIntent;
                     } else {
@@ -223,8 +239,7 @@
             }
         };
         try {
-            connection.getInstantAppIntentFilterList(sanitizedIntent,
-                    requestObj.digest.getDigestPrefixSecure(), requestObj.userId, token, callback,
+            connection.getInstantAppIntentFilterList(buildRequestInfo(requestObj), callback,
                     callbackHandler, startTime);
         } catch (ConnectionException e) {
             @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
@@ -356,10 +371,22 @@
         return intent;
     }
 
+    private static InstantAppRequestInfo buildRequestInfo(InstantAppRequest request) {
+        return new InstantAppRequestInfo(
+                sanitizeIntent(request.origIntent),
+                // This must only expose the secured version of the host
+                request.hostDigestPrefixSecure,
+                UserHandle.getUserHandleForUid(request.userId),
+                request.isRequesterInstantApp,
+                request.token
+        );
+    }
+
     private static AuxiliaryResolveInfo filterInstantAppIntent(
-            List<InstantAppResolveInfo> instantAppResolveInfoList,
-            Intent origIntent, String resolvedType, int userId, String packageName,
-            InstantAppDigest digest, String token) {
+            List<InstantAppResolveInfo> instantAppResolveInfoList, Intent origIntent,
+            String resolvedType, int userId, String packageName, String token,
+            int[] hostDigestPrefixSecure) {
+        InstantAppDigest digest = InstantAppResolver.parseDigest(origIntent);
         final int[] shaPrefix = digest.getDigestPrefix();
         final byte[][] digestBytes = digest.getDigestBytes();
         boolean requiresSecondPhase = false;
@@ -404,7 +431,7 @@
         }
         if (filters != null && !filters.isEmpty()) {
             return new AuxiliaryResolveInfo(token, requiresSecondPhase,
-                    createFailureIntent(origIntent, token), filters);
+                    createFailureIntent(origIntent, token), filters, hostDigestPrefixSecure);
         }
         // Hash or filter mis-match; no instant apps for this domain.
         return null;
diff --git a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
index c0e9f58..0fe2eb1 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.InstantAppRequestInfo;
 import android.content.pm.InstantAppResolveInfo;
 import android.os.Binder;
 import android.os.Build;
@@ -85,13 +86,13 @@
         mBgHandler = BackgroundThread.getHandler();
     }
 
-    public List<InstantAppResolveInfo> getInstantAppResolveInfoList(Intent sanitizedIntent,
-            int[] hashPrefix, int userId, String token) throws ConnectionException {
+    public List<InstantAppResolveInfo> getInstantAppResolveInfoList(InstantAppRequestInfo request)
+            throws ConnectionException {
         throwIfCalledOnMainThread();
         IInstantAppResolver target = null;
         try {
             try {
-                target = getRemoteInstanceLazy(token);
+                target = getRemoteInstanceLazy(request.token);
             } catch (TimeoutException e) {
                 throw new ConnectionException(ConnectionException.FAILURE_BIND);
             } catch (InterruptedException e) {
@@ -99,8 +100,7 @@
             }
             try {
                 return mGetInstantAppResolveInfoCaller
-                        .getInstantAppResolveInfoList(target, sanitizedIntent, hashPrefix, userId,
-                                token);
+                        .getInstantAppResolveInfoList(target, request);
             } catch (TimeoutException e) {
                 throw new ConnectionException(ConnectionException.FAILURE_CALL);
             } catch (RemoteException ignore) {
@@ -113,8 +113,8 @@
         return null;
     }
 
-    public void getInstantAppIntentFilterList(Intent sanitizedIntent, int[] hashPrefix, int userId,
-            String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime)
+    public void getInstantAppIntentFilterList(InstantAppRequestInfo request,
+            PhaseTwoCallback callback, Handler callbackHandler, final long startTime)
             throws ConnectionException {
         final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() {
             @Override
@@ -126,9 +126,8 @@
             }
         };
         try {
-            getRemoteInstanceLazy(token)
-                    .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, userId, token,
-                            remoteCallback);
+            getRemoteInstanceLazy(request.token)
+                    .getInstantAppIntentFilterList(request, remoteCallback);
         } catch (TimeoutException e) {
             throw new ConnectionException(ConnectionException.FAILURE_BIND);
         } catch (InterruptedException e) {
@@ -352,12 +351,10 @@
             };
         }
 
-        public List<InstantAppResolveInfo> getInstantAppResolveInfoList(
-                IInstantAppResolver target, Intent sanitizedIntent, int[] hashPrefix, int userId,
-                String token) throws RemoteException, TimeoutException {
+        public List<InstantAppResolveInfo> getInstantAppResolveInfoList(IInstantAppResolver target,
+                InstantAppRequestInfo request) throws RemoteException, TimeoutException {
             final int sequence = onBeforeRemoteCall();
-            target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, userId, token,
-                    sequence, mCallback);
+            target.getInstantAppResolveInfoList(request, sequence, mCallback);
             return getResultTimed(sequence);
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index dceca0a..a54534b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -637,7 +637,7 @@
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
                 installSource, params, createdMillis,
-                stageDir, stageCid, false, false, false, null, SessionInfo.INVALID_ID,
+                stageDir, stageCid, null, false, false, false, null, SessionInfo.INVALID_ID,
                 false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
 
         synchronized (mSessions) {
@@ -1014,12 +1014,28 @@
         }
     }
 
+    static void sendPendingStreaming(Context context, IntentSender target, int sessionId,
+            Throwable cause) {
+        final Intent intent = new Intent();
+        intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+        intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_STREAMING);
+        if (cause != null && !TextUtils.isEmpty(cause.getMessage())) {
+            intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
+                    "Staging Image Not Ready [" + cause.getMessage() + "]");
+        } else {
+            intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
+        }
+        try {
+            target.sendIntent(context, 0, intent, null, null);
+        } catch (SendIntentException ignored) {
+        }
+    }
+
     static void sendOnUserActionRequired(Context context, IntentSender target, int sessionId,
             Intent intent) {
         final Intent fillIn = new Intent();
         fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
-        fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_PENDING_USER_ACTION);
+        fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
         fillIn.putExtra(Intent.EXTRA_INTENT, intent);
         try {
             target.sendIntent(context, 0, fillIn, null, null);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 518ec50..286d291 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -21,6 +21,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
 import static android.content.pm.PackageParser.APEX_FILE_EXTENSION;
 import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
@@ -30,11 +31,13 @@
 
 import static com.android.internal.util.XmlUtils.readBitmapAttribute;
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readByteArrayAttribute;
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readLongAttribute;
 import static com.android.internal.util.XmlUtils.readStringAttribute;
 import static com.android.internal.util.XmlUtils.readUriAttribute;
 import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeByteArrayAttribute;
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -123,6 +126,7 @@
 import java.io.FileFilter;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
@@ -142,6 +146,7 @@
     /** XML constants used for persisting a session */
     static final String TAG_SESSION = "session";
     static final String TAG_CHILD_SESSION = "childSession";
+    static final String TAG_SESSION_FILE = "sessionFile";
     private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
     private static final String TAG_WHITELISTED_RESTRICTED_PERMISSION =
             "whitelisted-restricted-permission";
@@ -183,6 +188,9 @@
     private static final String ATTR_VOLUME_UUID = "volumeUuid";
     private static final String ATTR_NAME = "name";
     private static final String ATTR_INSTALL_REASON = "installRason";
+    private static final String ATTR_DATA_LOADER_PACKAGE_NAME = "dataLoaderPackageName";
+    private static final String ATTR_LENGTH_BYTES = "lengthBytes";
+    private static final String ATTR_METADATA = "metadata";
 
     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
     private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
@@ -278,6 +286,29 @@
     @GuardedBy("mLock")
     private int mParentSessionId;
 
+    static class FileInfo {
+        public final String name;
+        public final Long lengthBytes;
+        public final byte[] metadata;
+
+        public static FileInfo added(String name, Long lengthBytes, byte[] metadata) {
+            return new FileInfo(name, lengthBytes, metadata);
+        }
+
+        public static FileInfo removed(String name) {
+            return new FileInfo(name, -1L, null);
+        }
+
+        FileInfo(String name, Long lengthBytes, byte[] metadata) {
+            this.name = name;
+            this.lengthBytes = lengthBytes;
+            this.metadata = metadata;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private ArrayList<FileInfo> mFiles = new ArrayList<>();
+
     @GuardedBy("mLock")
     private boolean mStagedSessionApplied;
     @GuardedBy("mLock")
@@ -313,6 +344,7 @@
     @GuardedBy("mLock")
     private boolean mVerityFound;
 
+    // TODO(b/146080380): merge file list with Callback installation.
     private IncrementalFileStorages mIncrementalFileStorages;
 
     private static final FileFilter sAddedFilter = new FileFilter() {
@@ -344,7 +376,9 @@
             IntentSender statusReceiver;
             switch (msg.what) {
                 case MSG_SEAL:
-                    handleSeal((IntentSender) msg.obj);
+                    statusReceiver = (IntentSender) msg.obj;
+
+                    handleSeal(statusReceiver);
                     break;
                 case MSG_COMMIT:
                     handleCommit();
@@ -378,6 +412,10 @@
         }
     };
 
+    private boolean isDataLoaderInstallation() {
+        return !TextUtils.isEmpty(params.dataLoaderPackageName);
+    }
+
     /**
      * @return {@code true} iff the installing is app an device owner or affiliated profile owner.
      */
@@ -435,7 +473,8 @@
             PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
             int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
             SessionParams params, long createdMillis,
-            File stageDir, String stageCid, boolean prepared, boolean committed, boolean sealed,
+            File stageDir, String stageCid, FileInfo[] files, boolean prepared,
+            boolean committed, boolean sealed,
             @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
             boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
             String stagedSessionErrorMessage) {
@@ -464,6 +503,12 @@
         }
         this.mParentSessionId = parentSessionId;
 
+        if (files != null) {
+            for (FileInfo file : files) {
+                mFiles.add(file);
+            }
+        }
+
         if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) {
             throw new IllegalArgumentException(
                     "Exactly one of stageDir or stageCid stage must be set");
@@ -592,15 +637,19 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void setClientProgressLocked(float progress) {
+        // Always publish first staging movement
+        final boolean forcePublish = (mClientProgress == 0);
+        mClientProgress = progress;
+        computeProgressLocked(forcePublish);
+    }
+
     @Override
     public void setClientProgress(float progress) {
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
-
-            // Always publish first staging movement
-            final boolean forcePublish = (mClientProgress == 0);
-            mClientProgress = progress;
-            computeProgressLocked(forcePublish);
+            setClientProgressLocked(progress);
         }
     }
 
@@ -608,8 +657,7 @@
     public void addClientProgress(float progress) {
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
-
-            setClientProgress(mClientProgress + progress);
+            setClientProgressLocked(mClientProgress + progress);
         }
     }
 
@@ -637,7 +685,10 @@
 
     @GuardedBy("mLock")
     private String[] getNamesLocked() {
-        return stageDir.list();
+        if (!isDataLoaderInstallation()) {
+            return stageDir.list();
+        }
+        return mFiles.stream().map(fileInfo -> fileInfo.name).toArray(String[]::new);
     }
 
     private static File[] filterFiles(File parent, String[] names, FileFilter filter) {
@@ -659,6 +710,10 @@
 
     @Override
     public void removeSplit(String splitName) {
+        if (isDataLoaderInstallation()) {
+            throw new IllegalStateException(
+                    "Cannot remove splits in a callback installation session.");
+        }
         if (TextUtils.isEmpty(params.appPackageName)) {
             throw new IllegalStateException("Must specify package name to remove a split");
         }
@@ -693,8 +748,31 @@
         }
     }
 
+    private void assertCanWrite(boolean reverseMode) {
+        if (isDataLoaderInstallation()) {
+            throw new IllegalStateException(
+                    "Cannot write regular files in a callback installation session.");
+        }
+        synchronized (mLock) {
+            assertCallerIsOwnerOrRootLocked();
+            assertPreparedAndNotSealedLocked("assertCanWrite");
+        }
+        if (reverseMode) {
+            switch (Binder.getCallingUid()) {
+                case android.os.Process.SHELL_UID:
+                case android.os.Process.ROOT_UID:
+                case android.os.Process.SYSTEM_UID:
+                    break;
+                default:
+                    throw new SecurityException(
+                            "Reverse mode only supported from shell or system");
+            }
+        }
+    }
+
     @Override
     public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
+        assertCanWrite(false);
         try {
             return doWriteInternal(name, offsetBytes, lengthBytes, null);
         } catch (IOException e) {
@@ -705,6 +783,7 @@
     @Override
     public void write(String name, long offsetBytes, long lengthBytes,
             ParcelFileDescriptor fd) {
+        assertCanWrite(fd != null);
         try {
             doWriteInternal(name, offsetBytes, lengthBytes, fd);
         } catch (IOException e) {
@@ -720,9 +799,6 @@
         final RevocableFileDescriptor fd;
         final FileBridge bridge;
         synchronized (mLock) {
-            assertCallerIsOwnerOrRootLocked();
-            assertPreparedAndNotSealedLocked("openWrite");
-
             if (PackageInstaller.ENABLE_REVOCABLE_FD) {
                 fd = new RevocableFileDescriptor();
                 bridge = null;
@@ -765,16 +841,6 @@
             }
 
             if (incomingFd != null) {
-                switch (Binder.getCallingUid()) {
-                    case android.os.Process.SHELL_UID:
-                    case android.os.Process.ROOT_UID:
-                    case android.os.Process.SYSTEM_UID:
-                        break;
-                    default:
-                        throw new SecurityException(
-                                "Reverse mode only supported from shell or system");
-                }
-
                 // In "reverse" mode, we're streaming data ourselves from the
                 // incoming FD, which means we never have to hand out our
                 // sensitive internal FD. We still rely on a "bridge" being
@@ -786,7 +852,10 @@
                                 if (params.sizeBytes > 0) {
                                     final long delta = progress - last.value;
                                     last.value = progress;
-                                    addClientProgress((float) delta / (float) params.sizeBytes);
+                                    synchronized (mLock) {
+                                        setClientProgressLocked(mClientProgress
+                                                + (float) delta / (float) params.sizeBytes);
+                                    }
                                 }
                             });
                 } finally {
@@ -821,6 +890,10 @@
 
     @Override
     public ParcelFileDescriptor openRead(String name) {
+        if (isDataLoaderInstallation()) {
+            throw new IllegalStateException(
+                    "Cannot read regular files in a callback installation session.");
+        }
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotCommittedOrDestroyedLocked("openRead");
@@ -926,6 +999,7 @@
                 return;
             }
         }
+
         mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
     }
 
@@ -988,6 +1062,14 @@
         }
     }
 
+    /** {@hide} */
+    private class StreamingException extends Exception {
+        StreamingException(Throwable cause) {
+            super(cause);
+        }
+    }
+
+
     /**
      * Sanity checks to make sure it's ok to commit the session.
      */
@@ -1039,12 +1121,22 @@
             }
 
             wasSealed = mSealed;
-            if (!mSealed) {
+            try {
+                if (!mSealed) {
+                    sealLocked(childSessions);
+                }
+
                 try {
-                    sealAndValidateLocked(childSessions);
-                } catch (PackageManagerException e) {
+                    streamAndValidateLocked();
+                } catch (StreamingException e) {
+                    // In case of streaming failure we don't want to fail or commit the session.
+                    // Just return from this method and allow caller to commit again.
+                    PackageInstallerService.sendPendingStreaming(mContext, mRemoteStatusReceiver,
+                            sessionId, e);
                     return false;
                 }
+            } catch (PackageManagerException e) {
+                return false;
             }
 
             // Client staging is fully done at this point
@@ -1131,17 +1223,26 @@
     }
 
     /**
-     * Seal the session to prevent further modification and validate the contents of it.
+     * Convenience wrapper, see {@link #sealLocked(List<PackageInstallerSession>) seal} and
+     * {@link #streamAndValidateLocked()}.
+     */
+    @GuardedBy("mLock")
+    private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
+            throws PackageManagerException, StreamingException {
+        sealLocked(childSessions);
+        streamAndValidateLocked();
+    }
+
+    /**
+     * Seal the session to prevent further modification.
      *
      * <p>The session will be sealed after calling this method even if it failed.
      *
-     * @param childSessions the child sessions of a multipackage that will be checked for
-     *                      consistency. Can be null if session is not multipackage.
      * @throws PackageManagerException if the session was sealed but something went wrong. If the
      *                                 session was sealed this is the only possible exception.
      */
     @GuardedBy("mLock")
-    private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
+    private void sealLocked(List<PackageInstallerSession> childSessions)
             throws PackageManagerException {
         try {
             assertNoWriteFileTransfersOpenLocked();
@@ -1152,7 +1253,26 @@
             if (childSessions != null) {
                 assertMultiPackageConsistencyLocked(childSessions);
             }
+        } catch (PackageManagerException e) {
+            throw onSessionVerificationFailure(e);
+        } catch (Throwable e) {
+            // Convert all exceptions into package manager exceptions as only those are handled
+            // in the code above.
+            throw onSessionVerificationFailure(new PackageManagerException(e));
+        }
+    }
 
+    /**
+     * Prepare DataLoader and stream content for DataLoader sessions.
+     * Validate the contents of all session.
+     *
+     * @throws StreamingException if streaming failed.
+     * @throws PackageManagerException if validation failed.
+     */
+    @GuardedBy("mLock")
+    private void streamAndValidateLocked()
+            throws PackageManagerException, StreamingException {
+        try {
             // Read transfers from the original owner stay open, but as the session's data cannot
             // be modified anymore, there is no leak of information. For staged sessions, further
             // validation is performed by the staging manager.
@@ -1161,6 +1281,8 @@
                         params.appPackageName, PackageManager.GET_SIGNATURES
                                 | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
 
+                prepareDataLoader();
+
                 if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
                     validateApexInstallLocked();
                 } else {
@@ -1173,6 +1295,8 @@
             }
         } catch (PackageManagerException e) {
             throw onSessionVerificationFailure(e);
+        } catch (StreamingException e) {
+            throw e;
         } catch (Throwable e) {
             // Convert all exceptions into package manager exceptions as only those are handled
             // in the code above.
@@ -1208,6 +1332,8 @@
         synchronized (mLock) {
             try {
                 sealAndValidateLocked(childSessions);
+            } catch (StreamingException e) {
+                Slog.e(TAG, "Streaming failed", e);
             } catch (PackageManagerException e) {
                 Slog.e(TAG, "Package not valid", e);
             }
@@ -1277,6 +1403,8 @@
 
                 try {
                     sealAndValidateLocked(childSessions);
+                } catch (StreamingException e) {
+                    throw new IllegalArgumentException("Streaming failed", e);
                 } catch (PackageManagerException e) {
                     throw new IllegalArgumentException("Package is not valid", e);
                 }
@@ -1517,9 +1645,10 @@
                 mInternalProgress = 0.5f;
                 computeProgressLocked(true);
 
-                // Unpack native libraries
-                // TODO(b/136132412): skip for incremental installation
-                extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
+                // Unpack native libraries for non-incremental installation
+                if (params.incrementalParams == null) {
+                    extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
+                }
             }
 
             // We've reached point of no return; call into PMS to install the stage.
@@ -2223,6 +2352,132 @@
     }
 
     @Override
+    public void addFile(String name, long lengthBytes, byte[] metadata) {
+        if (mIncrementalFileStorages != null) {
+            try {
+                mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata));
+            } 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-callback installation session.");
+        }
+        // Use installer provided name for now; we always rename later
+        if (!FileUtils.isValidExtFilename(name)) {
+            throw new IllegalArgumentException("Invalid name: " + name);
+        }
+
+        synchronized (mLock) {
+            assertCallerIsOwnerOrRootLocked();
+            assertPreparedAndNotSealedLocked("addFile");
+
+            mFiles.add(FileInfo.added(name, lengthBytes, metadata));
+        }
+    }
+
+    @Override
+    public void removeFile(String name) {
+        if (!isDataLoaderInstallation()) {
+            throw new IllegalStateException(
+                    "Cannot add files to non-callback installation session.");
+        }
+        if (TextUtils.isEmpty(params.appPackageName)) {
+            throw new IllegalStateException("Must specify package name to remove a split");
+        }
+
+        synchronized (mLock) {
+            assertCallerIsOwnerOrRootLocked();
+            assertPreparedAndNotSealedLocked("removeFile");
+
+            mFiles.add(FileInfo.removed(getRemoveMarkerName(name)));
+        }
+    }
+
+    /**
+     * Makes sure files are present in staging location.
+     */
+    private void prepareDataLoader()
+            throws PackageManagerException, StreamingException {
+        if (!isDataLoaderInstallation()) {
+            return;
+        }
+
+        FilesystemConnector connector = new FilesystemConnector();
+
+        FileInfo[] addedFiles = mFiles.stream().filter(
+                file -> sAddedFilter.accept(new File(file.name))).toArray(FileInfo[]::new);
+        String[] removedFiles = mFiles.stream().filter(
+                file -> sRemovedFilter.accept(new File(file.name))).map(
+                    file -> file.name.substring(0,
+                        file.name.length() - REMOVE_MARKER_EXTENSION.length())).toArray(
+                String[]::new);
+
+        DataLoader dataLoader = new DataLoader();
+        try {
+            dataLoader.onCreate(connector);
+
+            if (!dataLoader.onPrepareImage(addedFiles, removedFiles)) {
+                throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+                        "Failed to prepare image.");
+            }
+        } catch (IOException e) {
+            throw new StreamingException(e);
+        } finally {
+            dataLoader.onDestroy();
+        }
+    }
+
+    // TODO(b/146080380): implement DataLoader using Incremental infrastructure.
+    class FilesystemConnector {
+        void writeData(FileInfo fileInfo, long offset, long lengthBytes,
+                ParcelFileDescriptor incomingFd) throws IOException {
+            doWriteInternal(fileInfo.name, offset, lengthBytes, incomingFd);
+        }
+    }
+
+    static class DataLoader {
+        private ParcelFileDescriptor mInFd = null;
+        private FilesystemConnector mConnector = null;
+
+        void onCreate(FilesystemConnector connector) throws IOException {
+            mConnector = connector;
+        }
+
+        void onDestroy() {
+            IoUtils.closeQuietly(mInFd);
+        }
+
+        private static final String STDIN_PATH = "-";
+        boolean onPrepareImage(FileInfo[] addedFiles, String[] removedFiles) throws IOException {
+            for (FileInfo fileInfo : addedFiles) {
+                String filePath = new String(fileInfo.metadata, StandardCharsets.UTF_8);
+                if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
+                    if (mInFd == null) {
+                        Slog.e(TAG, "Invalid stdin file descriptor.");
+                        return false;
+                    }
+                    ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(mInFd.getFileDescriptor());
+                    mConnector.writeData(fileInfo, 0, fileInfo.lengthBytes, inFd);
+                } else {
+                    File localFile = new File(filePath);
+                    ParcelFileDescriptor incomingFd = null;
+                    try {
+                        incomingFd = ParcelFileDescriptor.open(localFile,
+                                ParcelFileDescriptor.MODE_READ_ONLY);
+                        mConnector.writeData(fileInfo, 0, localFile.length(), incomingFd);
+                    } finally {
+                        IoUtils.closeQuietly(incomingFd);
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    @Override
     public int[] getChildSessionIds() {
         final int[] childSessionIds = mChildSessionIds.copyKeys();
         if (childSessionIds != null) {
@@ -2294,20 +2549,6 @@
         return mParentSessionId;
     }
 
-    @Override
-    public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
-        if (mIncrementalFileStorages == null) {
-            throw new IllegalStateException(
-                    "Cannot add Incremental File to a non-Incremental session.");
-        }
-        try {
-            mIncrementalFileStorages.addFile(new InstallationFile(name, size, metadata));
-        } catch (IOException ex) {
-            throw new IllegalStateException(
-                    "Failed to add and configure Incremental File: " + name, ex);
-        }
-    }
-
     private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
         final IntentSender statusReceiver;
         final String packageName;
@@ -2320,7 +2561,7 @@
         }
 
         if (statusReceiver != null) {
-            // Execute observer.onPackageInstalled on different tread as we don't want callers
+            // Execute observer.onPackageInstalled on different thread as we don't want callers
             // inside the system server have to worry about catching the callbacks while they are
             // calling into the session
             final SomeArgs args = SomeArgs.obtain();
@@ -2594,6 +2835,8 @@
             writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
             writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
 
+            writeStringAttribute(out, ATTR_DATA_LOADER_PACKAGE_NAME, params.dataLoaderPackageName);
+
             writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions);
             writeWhitelistedRestrictedPermissionsLocked(out,
                     params.whitelistedRestrictedPermissions);
@@ -2623,6 +2866,13 @@
                 writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
                 out.endTag(null, TAG_CHILD_SESSION);
             }
+            for (FileInfo fileInfo : mFiles) {
+                out.startTag(null, TAG_SESSION_FILE);
+                writeStringAttribute(out, ATTR_NAME, fileInfo.name);
+                writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes);
+                writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata);
+                out.endTag(null, TAG_SESSION_FILE);
+            }
         }
 
         out.endTag(null, TAG_SESSION);
@@ -2696,6 +2946,8 @@
         params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
         params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
 
+        params.dataLoaderPackageName = readStringAttribute(in, ATTR_DATA_LOADER_PACKAGE_NAME);
+
         final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
         if (appIconFile.exists()) {
             params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
@@ -2722,6 +2974,7 @@
         List<String> grantedRuntimePermissions = new ArrayList<>();
         List<String> whitelistedRestrictedPermissions = new ArrayList<>();
         List<Integer> childSessionIds = new ArrayList<>();
+        List<FileInfo> files = new ArrayList<>();
         int outerDepth = in.getDepth();
         int type;
         while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -2739,6 +2992,11 @@
             if (TAG_CHILD_SESSION.equals(in.getName())) {
                 childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
             }
+            if (TAG_SESSION_FILE.equals(in.getName())) {
+                files.add(new FileInfo(readStringAttribute(in, ATTR_NAME),
+                        readLongAttribute(in, ATTR_LENGTH_BYTES, -1),
+                        readByteArrayAttribute(in, ATTR_METADATA)));
+            }
         }
 
         if (grantedRuntimePermissions.size() > 0) {
@@ -2757,11 +3015,16 @@
             childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
         }
 
+        FileInfo[] fileInfosArray = null;
+        if (!files.isEmpty()) {
+            fileInfosArray = files.stream().toArray(FileInfo[]::new);
+        }
+
         InstallSource installSource = InstallSource.create(installInitiatingPackageName,
                 installOriginatingPackageName, installerPackageName, false);
         return new PackageInstallerSession(callback, context, pm, sessionProvider,
                 installerThread, stagingManager, sessionId, userId, installerUid,
-                installSource, params, createdMillis, stageDir, stageCid,
+                installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
                 prepared, committed, sealed, childSessionIdsArray, parentSessionId,
                 isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 17b1daf..99d5e4a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -155,6 +155,7 @@
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.InstantAppInfo;
 import android.content.pm.InstantAppRequest;
+import android.content.pm.InstantAppResolveInfo.InstantAppDigest;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.KeySet;
@@ -376,6 +377,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -6143,10 +6145,12 @@
 
     private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
             Intent origIntent, String resolvedType, String callingPackage,
-            Bundle verificationBundle, int userId) {
+            boolean isRequesterInstantApp, Bundle verificationBundle, int userId) {
         final Message msg = mHandler.obtainMessage(INSTANT_APP_RESOLUTION_PHASE_TWO,
                 new InstantAppRequest(responseObj, origIntent, resolvedType,
-                        callingPackage, userId, verificationBundle, false /*resolveForStart*/));
+                        callingPackage, isRequesterInstantApp, userId, verificationBundle,
+                        false /*resolveForStart*/, responseObj.hostDigestPrefixSecure,
+                        responseObj.token));
         mHandler.sendMessage(msg);
     }
 
@@ -6765,8 +6769,10 @@
             }
         }
         if (addInstant) {
-            result = maybeAddInstantAppInstaller(
-                    result, intent, resolvedType, flags, userId, resolveForStart);
+            String callingPkgName = getInstantAppPackageName(filterCallingUid);
+            boolean isRequesterInstantApp = isInstantApp(callingPkgName, userId);
+            result = maybeAddInstantAppInstaller(result, intent, resolvedType, flags, userId,
+                    resolveForStart, isRequesterInstantApp);
         }
         if (sortResult) {
             Collections.sort(result, RESOLVE_PRIORITY_SORTER);
@@ -6777,7 +6783,8 @@
     }
 
     private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, Intent intent,
-            String resolvedType, int flags, int userId, boolean resolveForStart) {
+            String resolvedType, int flags, int userId, boolean resolveForStart,
+            boolean isRequesterInstantApp) {
         // first, check to see if we've got an instant app already installed
         final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0;
         ResolveInfo localInstantApp = null;
@@ -6825,10 +6832,13 @@
             if (localInstantApp == null) {
                 // we don't have an instant app locally, resolve externally
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveEphemeral");
+                String token = UUID.randomUUID().toString();
+                InstantAppDigest digest = InstantAppResolver.parseDigest(intent);
                 final InstantAppRequest requestObject = new InstantAppRequest(
                         null /*responseObj*/, intent /*origIntent*/, resolvedType,
-                        null /*callingPackage*/, userId, null /*verificationBundle*/,
-                        resolveForStart);
+                        null /*callingPackage*/, isRequesterInstantApp,
+                        userId, null /*verificationBundle*/, resolveForStart,
+                        digest.getDigestPrefixSecure(), token);
                 auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne(
                         mInstantAppResolverConnection, requestObject);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -14608,7 +14618,8 @@
                 return false;
             }
 
-            if (!SELinux.restoreconRecursive(afterCodeFile)) {
+            //TODO(b/136132412): enable selinux restorecon for incremental directories
+            if (!onIncremental && !SELinux.restoreconRecursive(afterCodeFile)) {
                 Slog.w(TAG, "Failed to restorecon");
                 return false;
             }
@@ -17951,14 +17962,6 @@
                 }
             }
             mPermissionManager.resetRuntimePermissions(pkg, nextUserId);
-            // Also delete contributed media, when requested
-            if ((flags & PackageManager.DELETE_CONTRIBUTED_MEDIA) != 0) {
-                try {
-                    MediaStore.deleteContributedMedia(mContext, ps.name, UserHandle.of(nextUserId));
-                } catch (IOException e) {
-                    Slog.w(TAG, "Failed to delete contributed media for " + ps.name, e);
-                }
-            }
         }
 
         if (outInfo != null) {
@@ -22747,6 +22750,12 @@
         }
 
         @Override
+        public ComponentName getSystemUiServiceComponent() {
+            return ComponentName.unflattenFromString(mContext.getResources().getString(
+                    com.android.internal.R.string.config_systemUIServiceComponent));
+        }
+
+        @Override
         public void setDeviceAndProfileOwnerPackages(
                 int deviceOwnerUserId, String deviceOwnerPackage,
                 SparseArray<String> profileOwnerPackages) {
@@ -22826,10 +22835,10 @@
         @Override
         public void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
                 Intent origIntent, String resolvedType, String callingPackage,
-                Bundle verificationBundle, int userId) {
+                boolean isRequesterInstantApp, Bundle verificationBundle, int userId) {
             PackageManagerService.this.requestInstantAppResolutionPhaseTwo(
-                    responseObj, origIntent, resolvedType, callingPackage, verificationBundle,
-                    userId);
+                    responseObj, origIntent, resolvedType, callingPackage, isRequesterInstantApp,
+                    verificationBundle, userId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index fff404f..dfffbd6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -114,6 +114,7 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -135,6 +136,8 @@
     private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
     private static final int DEFAULT_WAIT_MS = 60 * 1000;
 
+    private static final String PM_SHELL_DATALOADER = "com.android.pm.dataloader";
+
     final IPackageManager mInterface;
     final IPermissionManager mPermissionManager;
     final private WeakHashMap<String, Resources> mResourceCache =
@@ -175,6 +178,8 @@
                     return runQueryIntentReceivers();
                 case "install":
                     return runInstall();
+                case "install-streaming":
+                    return runStreamingInstall();
                 case "install-abandon":
                 case "install-destroy":
                     return runInstallAbandon();
@@ -1152,9 +1157,21 @@
         return 0;
     }
 
-    private int runInstall() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
+    private int runStreamingInstall() throws RemoteException {
         final InstallParams params = makeInstallParams();
+        if (TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName)) {
+            params.sessionParams.setDataLoaderPackageName(PM_SHELL_DATALOADER);
+        }
+        return doRunInstall(params);
+    }
+
+    private int runInstall() throws RemoteException {
+        return doRunInstall(makeInstallParams());
+    }
+
+    private int doRunInstall(final InstallParams params) throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final boolean streaming = !TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName);
 
         ArrayList<String> inPaths = getRemainingArgs();
         if (inPaths.isEmpty()) {
@@ -1181,17 +1198,30 @@
             return 1;
         }
 
-        setParamsSize(params, inPaths);
+        if (!streaming) {
+            setParamsSize(params, inPaths);
+        }
+
         final int sessionId = doCreateSession(params.sessionParams,
                 params.installerPackageName, params.userId);
         boolean abandonSession = true;
         try {
             for (String inPath : inPaths) {
-                String splitName = hasSplits ? (new File(inPath)).getName()
-                        : "base." + (isApex ? "apex" : "apk");
-                if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
-                        false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
-                    return 1;
+                if (streaming) {
+                    String name = new File(inPath).getName();
+                    byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8);
+                    if (doAddFile(sessionId, name, params.sessionParams.sizeBytes, metadata,
+                            false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+                        return 1;
+                    }
+                } else {
+                    String splitName = hasSplits ? new File(inPath).getName()
+                            : "base." + (isApex ? "apex" : "apk");
+
+                    if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
+                            false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+                        return 1;
+                    }
                 }
             }
             if (doCommitSession(sessionId, false /*logSuccess*/)
@@ -2927,11 +2957,32 @@
         return sessionId;
     }
 
+    private int doAddFile(int sessionId, String name, long sizeBytes, byte[] metadata,
+            boolean logSuccess) throws RemoteException {
+        PackageInstaller.Session session = new PackageInstaller.Session(
+                mInterface.getPackageInstaller().openSession(sessionId));
+        try {
+            session.addFile(name, sizeBytes, metadata);
+
+            if (logSuccess) {
+                getOutPrintWriter().println("Success");
+            }
+
+            return 0;
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+    }
+
     private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
             boolean logSuccess) throws RemoteException {
         PackageInstaller.Session session = null;
         try {
+            session = new PackageInstaller.Session(
+                    mInterface.getPackageInstaller().openSession(sessionId));
+
             final PrintWriter pw = getOutPrintWriter();
+
             final ParcelFileDescriptor fd;
             if (STDIN_PATH.equals(inPath)) {
                 fd = ParcelFileDescriptor.dup(getInFileDescriptor());
@@ -2953,8 +3004,6 @@
                 return 1;
             }
 
-            session = new PackageInstaller.Session(
-                    mInterface.getPackageInstaller().openSession(sessionId));
             session.write(splitName, 0, sizeBytes, fd);
 
             if (logSuccess) {
@@ -3000,7 +3049,6 @@
         try {
             session = new PackageInstaller.Session(
                     mInterface.getPackageInstaller().openSession(sessionId));
-
             for (String splitName : splitNames) {
                 session.removeSplit(splitName);
             }
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index e8798ff..59a5804 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -24,7 +24,20 @@
       ]
     },
     {
-      "name": "PackageManagerShellCommandTest"
+      "name": "CtsContentTestCases",
+      "options": [
+        {
+          "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
+        }
+      ]
+    },
+    {
+      "name": "GtsSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "com.google.android.security.gts.PackageVerifierTest"
+        }
+      ]
     }
   ],
   "postsubmit": [
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4d436c0..5fabdb6 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -351,6 +351,7 @@
      * User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
      * that should be applied to all users, including guests. Only non-empty restriction bundles are
      * stored.
+     * The key is the user id of the user whom the restriction originated from.
      */
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mDevicePolicyGlobalUserRestrictions = new SparseArray<>();
@@ -364,6 +365,7 @@
     /**
      * User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
      * for each user. Only non-empty restriction bundles are stored.
+     * The key is the user id of the user whom the restriction originated from.
      */
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
@@ -1621,7 +1623,7 @@
     /**
      * See {@link UserManagerInternal#setDevicePolicyUserRestrictions}
      */
-    private void setDevicePolicyUserRestrictionsInner(@UserIdInt int userId,
+    private void setDevicePolicyUserRestrictionsInner(@UserIdInt int originatingUserId,
             @Nullable Bundle restrictions,
             @UserManagerInternal.OwnerType int restrictionOwnerType) {
         final Bundle global = new Bundle();
@@ -1635,16 +1637,16 @@
         synchronized (mRestrictionsLock) {
             // Update global and local restrictions if they were changed.
             globalChanged = updateRestrictionsIfNeededLR(
-                    userId, global, mDevicePolicyGlobalUserRestrictions);
+                    originatingUserId, global, mDevicePolicyGlobalUserRestrictions);
             localChanged = updateRestrictionsIfNeededLR(
-                    userId, local, mDevicePolicyLocalUserRestrictions);
+                    originatingUserId, local, mDevicePolicyLocalUserRestrictions);
 
             if (restrictionOwnerType == UserManagerInternal.OWNER_TYPE_DEVICE_OWNER) {
                 // Remember the global restriction owner userId to be able to make a distinction
                 // in getUserRestrictionSource on who set local policies.
-                mDeviceOwnerUserId = userId;
+                mDeviceOwnerUserId = originatingUserId;
             } else {
-                if (mDeviceOwnerUserId == userId) {
+                if (mDeviceOwnerUserId == originatingUserId) {
                     // When profile owner sets restrictions it passes null global bundle and we
                     // reset global restriction owner userId.
                     // This means this user used to have DO, but now the DO is gone and the user
@@ -1654,15 +1656,16 @@
             }
         }
         if (DBG) {
-            Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: userId=" + userId
-                            + " global=" + global + (globalChanged ? " (changed)" : "")
-                            + " local=" + local + (localChanged ? " (changed)" : "")
+            Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: "
+                    + " originatingUserId=" + originatingUserId
+                    + " global=" + global + (globalChanged ? " (changed)" : "")
+                    + " local=" + local + (localChanged ? " (changed)" : "")
             );
         }
         // Don't call them within the mRestrictionsLock.
         synchronized (mPackagesLock) {
             if (localChanged || globalChanged) {
-                writeUserLP(getUserDataNoChecks(userId));
+                writeUserLP(getUserDataNoChecks(originatingUserId));
             }
         }
 
@@ -1670,7 +1673,7 @@
             if (globalChanged) {
                 applyUserRestrictionsForAllUsersLR();
             } else if (localChanged) {
-                applyUserRestrictionsLR(userId);
+                applyUserRestrictionsLR(originatingUserId);
             }
         }
     }
@@ -4507,9 +4510,10 @@
 
     private class LocalService extends UserManagerInternal {
         @Override
-        public void setDevicePolicyUserRestrictions(@UserIdInt int userId,
-                @Nullable Bundle restrictions, @OwnerType int restrictionOwnerType) {
-            UserManagerService.this.setDevicePolicyUserRestrictionsInner(userId,
+        public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId,
+                @Nullable Bundle restrictions,
+                @OwnerType int restrictionOwnerType) {
+            UserManagerService.this.setDevicePolicyUserRestrictionsInner(originatingUserId,
                     restrictions, restrictionOwnerType);
         }
 
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 0beff7a..e0bd0b4 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -411,6 +411,13 @@
     }
 
     /**
+     * @return true if a restriction is settable by profile owner of an organization owned device.
+     */
+    public static boolean canProfileOwnerOfOrganizationOwnedDeviceChange(String restriction) {
+        return PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS.contains(restriction);
+    }
+
+    /**
      * Returns the user restrictions that default to {@code true} for device owners.
      * These user restrictions are local, though. ie only for the device owner's user id.
      */
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 9324870..6cdfcff 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -58,6 +58,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.PackageWatchdog;
+import com.android.server.SystemConfig;
 import com.android.server.Watchdog;
 import com.android.server.pm.Installer;
 
@@ -1008,11 +1009,19 @@
                 installerPackageName) == PackageManager.PERMISSION_GRANTED;
 
         // For now only allow rollbacks for modules or for testing.
-        return (isModule(packageName) && manageRollbacksGranted)
+        return (isRollbackWhitelisted(packageName) && manageRollbacksGranted)
             || testManageRollbacksGranted;
     }
 
     /**
+     * Returns true is this package is eligible for enabling rollback.
+     */
+    private boolean isRollbackWhitelisted(String packageName) {
+        // TODO: Remove #isModule when the white list is ready.
+        return SystemConfig.getInstance().getRollbackWhitelistedPackages().contains(packageName)
+                || isModule(packageName);
+    }
+    /**
      * Returns true if the package name is the name of a module.
      */
     private boolean isModule(String packageName) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
new file mode 100644
index 0000000..3fa5230
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+/**
+ * An implementation of SoundTriggerMiddlewareImpl.AudioSessionProvider that ties to native
+ * AudioSystem module via JNI.
+ */
+class AudioSessionProviderImpl extends SoundTriggerMiddlewareImpl.AudioSessionProvider {
+    @Override
+    public native AudioSession acquireSession();
+
+    @Override
+    public native void releaseSession(int sessionHandle);
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
new file mode 100644
index 0000000..9b22f33
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -0,0 +1,390 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.media.audio.common.AudioConfig;
+import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemoryUtil;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for type conversion between SoundTrigger HAL types and SoundTriggerMiddleware service
+ * types.
+ *
+ * @hide
+ */
+class ConversionUtil {
+    static @NonNull
+    SoundTriggerModuleProperties hidl2aidlProperties(
+            @NonNull ISoundTriggerHw.Properties hidlProperties) {
+        SoundTriggerModuleProperties aidlProperties = new SoundTriggerModuleProperties();
+        aidlProperties.implementor = hidlProperties.implementor;
+        aidlProperties.description = hidlProperties.description;
+        aidlProperties.version = hidlProperties.version;
+        aidlProperties.uuid = hidl2aidlUuid(hidlProperties.uuid);
+        aidlProperties.maxSoundModels = hidlProperties.maxSoundModels;
+        aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases;
+        aidlProperties.maxUsers = hidlProperties.maxUsers;
+        aidlProperties.recognitionModes = hidlProperties.recognitionModes;
+        aidlProperties.captureTransition = hidlProperties.captureTransition;
+        aidlProperties.maxBufferMs = hidlProperties.maxBufferMs;
+        aidlProperties.concurrentCapture = hidlProperties.concurrentCapture;
+        aidlProperties.triggerInEvent = hidlProperties.triggerInEvent;
+        aidlProperties.powerConsumptionMw = hidlProperties.powerConsumptionMw;
+        return aidlProperties;
+    }
+
+    static @NonNull
+    String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
+        if (hidlUuid.node == null || hidlUuid.node.length != 6) {
+            throw new IllegalArgumentException("UUID.node must be of length 6.");
+        }
+        return String.format(UuidUtil.FORMAT,
+                hidlUuid.timeLow,
+                hidlUuid.timeMid,
+                hidlUuid.versionAndTimeHigh,
+                hidlUuid.variantAndClockSeqHigh,
+                hidlUuid.node[0],
+                hidlUuid.node[1],
+                hidlUuid.node[2],
+                hidlUuid.node[3],
+                hidlUuid.node[4],
+                hidlUuid.node[5]);
+    }
+
+    static @NonNull
+    Uuid aidl2hidlUuid(@NonNull String aidlUuid) {
+        Matcher matcher = UuidUtil.PATTERN.matcher(aidlUuid);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("Illegal format for UUID: " + aidlUuid);
+        }
+        Uuid hidlUuid = new Uuid();
+        hidlUuid.timeLow = Integer.parseUnsignedInt(matcher.group(1), 16);
+        hidlUuid.timeMid = (short) Integer.parseUnsignedInt(matcher.group(2), 16);
+        hidlUuid.versionAndTimeHigh = (short) Integer.parseUnsignedInt(matcher.group(3), 16);
+        hidlUuid.variantAndClockSeqHigh = (short) Integer.parseUnsignedInt(matcher.group(4), 16);
+        hidlUuid.node = new byte[]{(byte) Integer.parseUnsignedInt(matcher.group(5), 16),
+                (byte) Integer.parseUnsignedInt(matcher.group(6), 16),
+                (byte) Integer.parseUnsignedInt(matcher.group(7), 16),
+                (byte) Integer.parseUnsignedInt(matcher.group(8), 16),
+                (byte) Integer.parseUnsignedInt(matcher.group(9), 16),
+                (byte) Integer.parseUnsignedInt(matcher.group(10), 16)};
+        return hidlUuid;
+    }
+
+    static int aidl2hidlSoundModelType(int aidlType) {
+        switch (aidlType) {
+            case SoundModelType.GENERIC:
+                return android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC;
+            case SoundModelType.KEYPHRASE:
+                return android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE;
+            default:
+                throw new IllegalArgumentException("Unknown sound model type: " + aidlType);
+        }
+    }
+
+    static int hidl2aidlSoundModelType(int hidlType) {
+        switch (hidlType) {
+            case android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC:
+                return SoundModelType.GENERIC;
+            case android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE:
+                return SoundModelType.KEYPHRASE;
+            default:
+                throw new IllegalArgumentException("Unknown sound model type: " + hidlType);
+        }
+    }
+
+    static @NonNull
+    ISoundTriggerHw.Phrase aidl2hidlPhrase(@NonNull Phrase aidlPhrase) {
+        ISoundTriggerHw.Phrase hidlPhrase = new ISoundTriggerHw.Phrase();
+        hidlPhrase.id = aidlPhrase.id;
+        hidlPhrase.recognitionModes = aidl2hidlRecognitionModes(aidlPhrase.recognitionModes);
+        for (int aidlUser : aidlPhrase.users) {
+            hidlPhrase.users.add(aidlUser);
+        }
+        hidlPhrase.locale = aidlPhrase.locale;
+        hidlPhrase.text = aidlPhrase.text;
+        return hidlPhrase;
+    }
+
+    static int aidl2hidlRecognitionModes(int aidlModes) {
+        int hidlModes = 0;
+
+        if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+            hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER;
+        }
+        if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+            hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION;
+        }
+        if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+            hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION;
+        }
+        if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+            hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+        }
+        return hidlModes;
+    }
+
+    static int hidl2aidlRecognitionModes(int hidlModes) {
+        int aidlModes = 0;
+        if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER) != 0) {
+            aidlModes |= RecognitionMode.VOICE_TRIGGER;
+        }
+        if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION)
+                != 0) {
+            aidlModes |= RecognitionMode.USER_IDENTIFICATION;
+        }
+        if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION)
+                != 0) {
+            aidlModes |= RecognitionMode.USER_AUTHENTICATION;
+        }
+        if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER) != 0) {
+            aidlModes |= RecognitionMode.GENERIC_TRIGGER;
+        }
+        return aidlModes;
+    }
+
+    static @NonNull
+    ISoundTriggerHw.SoundModel aidl2hidlSoundModel(@NonNull SoundModel aidlModel) {
+        ISoundTriggerHw.SoundModel hidlModel = new ISoundTriggerHw.SoundModel();
+        hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type);
+        hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid);
+        hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid);
+        hidlModel.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlModel.data,
+                "SoundTrigger SoundModel");
+        return hidlModel;
+    }
+
+    static @NonNull
+    ISoundTriggerHw.PhraseSoundModel aidl2hidlPhraseSoundModel(
+            @NonNull PhraseSoundModel aidlModel) {
+        ISoundTriggerHw.PhraseSoundModel hidlModel = new ISoundTriggerHw.PhraseSoundModel();
+        hidlModel.common = aidl2hidlSoundModel(aidlModel.common);
+        for (Phrase aidlPhrase : aidlModel.phrases) {
+            hidlModel.phrases.add(aidl2hidlPhrase(aidlPhrase));
+        }
+        return hidlModel;
+    }
+
+    static @NonNull
+    ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+            @NonNull RecognitionConfig aidlConfig) {
+        ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
+        hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+        for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
+            hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+        }
+        hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+                "SoundTrigger RecognitionConfig");
+        return hidlConfig;
+    }
+
+    static @NonNull
+    android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra aidl2hidlPhraseRecognitionExtra(
+            @NonNull PhraseRecognitionExtra aidlExtra) {
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra =
+                new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+        hidlExtra.id = aidlExtra.id;
+        hidlExtra.recognitionModes = aidl2hidlRecognitionModes(aidlExtra.recognitionModes);
+        hidlExtra.confidenceLevel = aidlExtra.confidenceLevel;
+        hidlExtra.levels.ensureCapacity(aidlExtra.levels.length);
+        for (ConfidenceLevel aidlLevel : aidlExtra.levels) {
+            hidlExtra.levels.add(aidl2hidlConfidenceLevel(aidlLevel));
+        }
+        return hidlExtra;
+    }
+
+    static @NonNull
+    PhraseRecognitionExtra hidl2aidlPhraseRecognitionExtra(
+            @NonNull android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra) {
+        PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+        aidlExtra.id = hidlExtra.id;
+        aidlExtra.recognitionModes = hidl2aidlRecognitionModes(hidlExtra.recognitionModes);
+        aidlExtra.confidenceLevel = hidlExtra.confidenceLevel;
+        aidlExtra.levels = new ConfidenceLevel[hidlExtra.levels.size()];
+        for (int i = 0; i < hidlExtra.levels.size(); ++i) {
+            aidlExtra.levels[i] = hidl2aidlConfidenceLevel(hidlExtra.levels.get(i));
+        }
+        return aidlExtra;
+    }
+
+    static @NonNull
+    android.hardware.soundtrigger.V2_0.ConfidenceLevel aidl2hidlConfidenceLevel(
+            @NonNull ConfidenceLevel aidlLevel) {
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel =
+                new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+        hidlLevel.userId = aidlLevel.userId;
+        hidlLevel.levelPercent = aidlLevel.levelPercent;
+        return hidlLevel;
+    }
+
+    static @NonNull
+    ConfidenceLevel hidl2aidlConfidenceLevel(
+            @NonNull android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel) {
+        ConfidenceLevel aidlLevel = new ConfidenceLevel();
+        aidlLevel.userId = hidlLevel.userId;
+        aidlLevel.levelPercent = hidlLevel.levelPercent;
+        return aidlLevel;
+    }
+
+    static int hidl2aidlRecognitionStatus(int hidlStatus) {
+        switch (hidlStatus) {
+            case ISoundTriggerHwCallback.RecognitionStatus.SUCCESS:
+                return RecognitionStatus.SUCCESS;
+            case ISoundTriggerHwCallback.RecognitionStatus.ABORT:
+                return RecognitionStatus.ABORTED;
+            case ISoundTriggerHwCallback.RecognitionStatus.FAILURE:
+                return RecognitionStatus.FAILURE;
+            case 3: // This doesn't have a constant in HIDL.
+                return RecognitionStatus.FORCED;
+            default:
+                throw new IllegalArgumentException("Unknown recognition status: " + hidlStatus);
+        }
+    }
+
+    static @NonNull
+    RecognitionEvent hidl2aidlRecognitionEvent(@NonNull
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+        RecognitionEvent aidlEvent = new RecognitionEvent();
+        aidlEvent.status = hidl2aidlRecognitionStatus(hidlEvent.status);
+        aidlEvent.type = hidl2aidlSoundModelType(hidlEvent.type);
+        aidlEvent.captureAvailable = hidlEvent.captureAvailable;
+        // hidlEvent.captureSession is never a valid field.
+        aidlEvent.captureSession = -1;
+        aidlEvent.captureDelayMs = hidlEvent.captureDelayMs;
+        aidlEvent.capturePreambleMs = hidlEvent.capturePreambleMs;
+        aidlEvent.triggerInData = hidlEvent.triggerInData;
+        aidlEvent.audioConfig = hidl2aidlAudioConfig(hidlEvent.audioConfig);
+        aidlEvent.data = new byte[hidlEvent.data.size()];
+        for (int i = 0; i < aidlEvent.data.length; ++i) {
+            aidlEvent.data[i] = hidlEvent.data.get(i);
+        }
+        return aidlEvent;
+    }
+
+    static @NonNull
+    RecognitionEvent hidl2aidlRecognitionEvent(
+            @NonNull ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+        RecognitionEvent aidlEvent = hidl2aidlRecognitionEvent(hidlEvent.header);
+        // Data needs to get overridden with 2.1 data.
+        aidlEvent.data = HidlMemoryUtil.hidlMemoryToByteArray(hidlEvent.data);
+        return aidlEvent;
+    }
+
+    static @NonNull
+    PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(@NonNull
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+        PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+        aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+        aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+        for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+            aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+                    hidlEvent.phraseExtras.get(i));
+        }
+        return aidlEvent;
+    }
+
+    static @NonNull
+    PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(
+            @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+        PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+        aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+        aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+        for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+            aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+                    hidlEvent.phraseExtras.get(i));
+        }
+        return aidlEvent;
+    }
+
+    static @NonNull
+    AudioConfig hidl2aidlAudioConfig(
+            @NonNull android.hardware.audio.common.V2_0.AudioConfig hidlConfig) {
+        AudioConfig aidlConfig = new AudioConfig();
+        // TODO(ytai): channelMask and format might need a more careful conversion to make sure the
+        //  constants match.
+        aidlConfig.sampleRateHz = hidlConfig.sampleRateHz;
+        aidlConfig.channelMask = hidlConfig.channelMask;
+        aidlConfig.format = hidlConfig.format;
+        aidlConfig.offloadInfo = hidl2aidlOffloadInfo(hidlConfig.offloadInfo);
+        aidlConfig.frameCount = hidlConfig.frameCount;
+        return aidlConfig;
+    }
+
+    static @NonNull
+    AudioOffloadInfo hidl2aidlOffloadInfo(
+            @NonNull android.hardware.audio.common.V2_0.AudioOffloadInfo hidlInfo) {
+        AudioOffloadInfo aidlInfo = new AudioOffloadInfo();
+        // TODO(ytai): channelMask, format, streamType and usage might need a more careful
+        //  conversion to make sure the constants match.
+        aidlInfo.sampleRateHz = hidlInfo.sampleRateHz;
+        aidlInfo.channelMask = hidlInfo.channelMask;
+        aidlInfo.format = hidlInfo.format;
+        aidlInfo.streamType = hidlInfo.streamType;
+        aidlInfo.bitRatePerSecond = hidlInfo.bitRatePerSecond;
+        aidlInfo.durationMicroseconds = hidlInfo.durationMicroseconds;
+        aidlInfo.hasVideo = hidlInfo.hasVideo;
+        aidlInfo.isStreaming = hidlInfo.isStreaming;
+        aidlInfo.bitWidth = hidlInfo.bitWidth;
+        aidlInfo.bufferSize = hidlInfo.bufferSize;
+        aidlInfo.usage = hidlInfo.usage;
+        return aidlInfo;
+    }
+
+    @Nullable
+    static ModelParameterRange hidl2aidlModelParameterRange(
+            android.hardware.soundtrigger.V2_3.ModelParameterRange hidlRange) {
+        if (hidlRange == null) {
+            return null;
+        }
+        ModelParameterRange aidlRange = new ModelParameterRange();
+        aidlRange.minInclusive = hidlRange.start;
+        aidlRange.maxInclusive = hidlRange.end;
+        return aidlRange;
+    }
+
+    static int aidl2hidlModelParameter(int aidlParam) {
+        switch (aidlParam) {
+            case ModelParameter.THRESHOLD_FACTOR:
+                return android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR;
+
+            default:
+                return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalException.java b/services/core/java/com/android/server/soundtrigger_middleware/HalException.java
new file mode 100644
index 0000000..8b3e708
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a non-zero status code returned by a HAL invocation.
+ * Depending on the operation that threw the error, the integrity of the HAL implementation and the
+ * client's tolerance to error, this error may or may not be recoverable. The HAL itself is expected
+ * to retain the state it had prior to the invocation (so, unless the error is a result of a HAL
+ * bug, normal operation may resume).
+ * <p>
+ * The reason why this is a RuntimeException, even though the HAL interface allows returning them
+ * is because we expect none of them to actually occur as part of correct usage of the HAL.
+ *
+ * @hide
+ */
+public class HalException extends RuntimeException {
+    public final int errorCode;
+
+    public HalException(int errorCode, @NonNull String message) {
+        super(message);
+        this.errorCode = errorCode;
+    }
+
+    public HalException(int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    @Override
+    public @NonNull String toString() {
+        return super.toString() + " (code " + errorCode + ")";
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
new file mode 100644
index 0000000..f0a0d83
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -0,0 +1,77 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.os.HidlMemoryUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Utilities for maintaining data compatibility between different minor versions of soundtrigger@2.x
+ * HAL.
+ * Note that some of these conversion utilities are destructive, i.e. mutate their input (for the
+ * sake of simplifying code and reducing copies).
+ */
+class Hw2CompatUtil {
+    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel convertSoundModel_2_1_to_2_0(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel) {
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 = soundModel.header;
+        // Note: this mutates the input!
+        model_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(soundModel.data);
+        return model_2_0;
+    }
+
+    static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent convertRecognitionEvent_2_0_to_2_1(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event) {
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+                new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+        event_2_1.header = event;
+        event_2_1.data = HidlMemoryUtil.byteListToHidlMemory(event_2_1.header.data,
+                "SoundTrigger RecognitionEvent");
+        // Note: this mutates the input!
+        event_2_1.header.data = new ArrayList<>();
+        return event_2_1;
+    }
+
+    static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent convertPhraseRecognitionEvent_2_0_to_2_1(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event) {
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+                event_2_1 =
+                new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+        event_2_1.common = convertRecognitionEvent_2_0_to_2_1(event.common);
+        event_2_1.phraseExtras = event.phraseExtras;
+        return event_2_1;
+    }
+
+    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel convertPhraseSoundModel_2_1_to_2_0(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel) {
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+                new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel();
+        model_2_0.common = convertSoundModel_2_1_to_2_0(soundModel.common);
+        model_2_0.phrases = soundModel.phrases;
+        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) {
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
+                config.header;
+        // Note: this mutates the input!
+        config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+        return config_2_0;
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
new file mode 100644
index 0000000..81252c9
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.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;
+
+/**
+ * This interface mimics android.hardware.soundtrigger.V2_x.ISoundTriggerHw and
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHwCallback, with a few key differences:
+ * <ul>
+ * <li>Methods in the original interface generally have a status return value and potentially a
+ * second return value which is the actual return value. This is reflected via a synchronous
+ * callback, which is not very pleasant to work with. This interface replaces that pattern with
+ * the convention that a HalException is thrown for non-OK status, and then we can use the
+ * return value for the actual return value.
+ * <li>This interface will always include all the methods from the latest 2.x version (and thus
+ * from every 2.x version) interface, with the convention that unsupported methods throw a
+ * {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * <li>Cases where the original interface had multiple versions of a method representing the exact
+ * thing, or there exists a trivial conversion between the new and old version, this interface
+ * represents only the latest version, without any _version suffixes.
+ * <li>Removes some of the obscure IBinder methods.
+ * <li>No RemoteExceptions are specified. Some implementations of this interface may rethrow
+ * RemoteExceptions as RuntimeExceptions, some can guarantee handling them somehow and never throw
+ * them.
+ * <li>soundModelCallback has been removed, since nobody cares about it. Implementations are free
+ * to silently discard it.
+ * </ul>
+ * For cases where the client wants to explicitly handle specific versions of the underlying driver
+ * interface, they may call {@link #interfaceDescriptor()}.
+ * <p>
+ * <b>Note to maintainers</b>: This class must always be kept in sync with the latest 2.x version,
+ * so that clients have access to the entire functionality without having to burden themselves with
+ * compatibility, as much as possible.
+ */
+public interface ISoundTriggerHw2 {
+    /**
+     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+     */
+    android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+
+    /**
+     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
+     * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+     * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback)
+     */
+    int loadSoundModel(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+            SoundTriggerHw2Compat.Callback callback, int cookie);
+
+    /**
+     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadPhraseSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel,
+     * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+     * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback)
+     */
+    int loadPhraseSoundModel(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+            SoundTriggerHw2Compat.Callback callback, int cookie);
+
+    /**
+     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#unloadSoundModel(int)
+     */
+    void unloadSoundModel(int modelHandle);
+
+    /**
+     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopRecognition(int)
+     */
+    void stopRecognition(int modelHandle);
+
+    /**
+     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopAllRecognitions()
+     */
+    void stopAllRecognitions();
+
+    /**
+     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
+     * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+     * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
+     */
+    void startRecognition(int modelHandle,
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            SoundTriggerHw2Compat.Callback callback, int cookie);
+
+    /**
+     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getModelState(int)
+     */
+    void getModelState(int modelHandle);
+
+    /**
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getParameter(int, int,
+     * ISoundTriggerHw.getParameterCallback)
+     */
+    int getModelParameter(int modelHandle, int param);
+
+    /**
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#setParameter(int, int, int)
+     */
+    void setModelParameter(int modelHandle, int param, int value);
+
+    /**
+     * @return null if not supported.
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#queryParameter(int, int,
+     * ISoundTriggerHw.queryParameterCallback)
+     */
+    ModelParameterRange queryParameter(int modelHandle, int param);
+
+    /**
+     * @see IHwBinder#linkToDeath(IHwBinder.DeathRecipient, long)
+     */
+    boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie);
+
+    /**
+     * @see IHwBinder#unlinkToDeath(IHwBinder.DeathRecipient)
+     */
+    boolean unlinkToDeath(IHwBinder.DeathRecipient recipient);
+
+    /**
+     * @see IBase#interfaceDescriptor()
+     */
+    String interfaceDescriptor() throws android.os.RemoteException;
+
+    interface Callback {
+        /**
+         * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#recognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent,
+         * int)
+         */
+        void recognitionCallback(
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+                int cookie);
+
+        /**
+         * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#phraseRecognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent,
+         * int)
+         */
+        void phraseRecognitionCallback(
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+                int cookie);
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java
new file mode 100644
index 0000000..e1fb226
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java
@@ -0,0 +1,40 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * An internal server error.
+ * <p>
+ * This exception wraps any exception thrown from a service implementation, which is a result of a
+ * bug in the server implementation (or any of its dependencies).
+ * <p>
+ * Specifically, this type is excluded from the set of whitelisted exceptions that binder would
+ * tunnel to the client process, since these exceptions are ambiguous regarding whether the client
+ * had done something wrong or the server is buggy. For example, a client getting an
+ * IllegalArgumentException cannot easily determine whether they had provided illegal arguments to
+ * the method they were calling, or whether the method implementation provided illegal arguments to
+ * some method it was calling due to a bug.
+ *
+ * @hide
+ */
+public class InternalServerError extends RuntimeException {
+    public InternalServerError(@NonNull Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
new file mode 100644
index 0000000..8361850
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
@@ -0,0 +1,53 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a fault which:
+ * <ul>
+ * <li>Could not have been anticipated by a caller (i.e. is not a violation of any preconditions).
+ * <li>Is guaranteed to not have been caused any meaningful state change in the callee. The caller
+ *     may continue operation as if the call has never been made.
+ * </ul>
+ * <p>
+ * Some recoverable faults are permanent and some are transient / circumstantial, the specific error
+ * code can provide more information about the possible recovery options.
+ * <p>
+ * The reason why this is a RuntimeException is to allow it to go through interfaces defined by
+ * AIDL, which we have no control over.
+ *
+ * @hide
+ */
+public class RecoverableException extends RuntimeException {
+    public final int errorCode;
+
+    public RecoverableException(int errorCode, @NonNull String message) {
+        super(message);
+        this.errorCode = errorCode;
+    }
+
+    public RecoverableException(int errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    @Override
+    public @NonNull String toString() {
+        return super.toString() + " (code " + errorCode + ")";
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
new file mode 100644
index 0000000..4a852c4
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -0,0 +1,470 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * An implementation of {@link ISoundTriggerHw2}, on top of any
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHw implementation. This class hides away some of
+ * the details involved with retaining backward compatibility and adapts to the more pleasant syntax
+ * exposed by {@link ISoundTriggerHw2}, compared to the bare driver interface.
+ * <p>
+ * Exception handling:
+ * <ul>
+ * <li>All {@link RemoteException}s get rethrown as {@link RuntimeException}.
+ * <li>All HAL malfunctions get thrown as {@link HalException}.
+ * <li>All unsupported operations get thrown as {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * </ul>
+ */
+final class SoundTriggerHw2Compat implements ISoundTriggerHw2 {
+    private final @NonNull
+    IHwBinder mBinder;
+    private final @NonNull
+    android.hardware.soundtrigger.V2_0.ISoundTriggerHw mUnderlying_2_0;
+    private final @Nullable
+    android.hardware.soundtrigger.V2_1.ISoundTriggerHw mUnderlying_2_1;
+    private final @Nullable
+    android.hardware.soundtrigger.V2_2.ISoundTriggerHw mUnderlying_2_2;
+    private final @Nullable
+    android.hardware.soundtrigger.V2_3.ISoundTriggerHw mUnderlying_2_3;
+
+    public SoundTriggerHw2Compat(
+            @NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw underlying) {
+        this(underlying.asBinder());
+    }
+
+    public SoundTriggerHw2Compat(IHwBinder binder) {
+        Objects.requireNonNull(binder);
+
+        mBinder = binder;
+
+        // We want to share the proxy instances rather than create a separate proxy for every
+        // version, so we go down the versions in descending order to find the latest one supported,
+        // and then simply up-cast it to obtain all the versions that are earlier.
+
+        // Attempt 2.3
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3 =
+                android.hardware.soundtrigger.V2_3.ISoundTriggerHw.asInterface(binder);
+        if (as2_3 != null) {
+            mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = as2_3;
+            return;
+        }
+
+        // Attempt 2.2
+        android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2 =
+                android.hardware.soundtrigger.V2_2.ISoundTriggerHw.asInterface(binder);
+        if (as2_2 != null) {
+            mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = as2_2;
+            mUnderlying_2_3 = null;
+            return;
+        }
+
+        // Attempt 2.1
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1 =
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.asInterface(binder);
+        if (as2_1 != null) {
+            mUnderlying_2_0 = mUnderlying_2_1 = as2_1;
+            mUnderlying_2_2 = mUnderlying_2_3 = null;
+            return;
+        }
+
+        // Attempt 2.0
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0 =
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHw.asInterface(binder);
+        if (as2_0 != null) {
+            mUnderlying_2_0 = as2_0;
+            mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = null;
+            return;
+        }
+
+        throw new RuntimeException("Binder doesn't support ISoundTriggerHw@2.0");
+    }
+
+    private static void handleHalStatus(int status, String methodName) {
+        if (status != 0) {
+            throw new HalException(status, methodName);
+        }
+    }
+
+    @Override
+    public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+        try {
+            AtomicInteger retval = new AtomicInteger(-1);
+            AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+                    properties =
+                    new AtomicReference<>();
+            as2_0().getProperties(
+                    (r, p) -> {
+                        retval.set(r);
+                        properties.set(p);
+                    });
+            handleHalStatus(retval.get(), "getProperties");
+            return properties.get();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public int loadSoundModel(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+            Callback callback, int cookie) {
+        try {
+            AtomicInteger retval = new AtomicInteger(-1);
+            AtomicInteger handle = new AtomicInteger(0);
+            try {
+                as2_1().loadSoundModel_2_1(soundModel, new SoundTriggerCallback(callback), cookie,
+                        (r, h) -> {
+                            retval.set(r);
+                            handle.set(h);
+                        });
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                return loadSoundModel_2_0(soundModel, callback, cookie);
+            }
+            handleHalStatus(retval.get(), "loadSoundModel_2_1");
+            return handle.get();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public int loadPhraseSoundModel(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+            Callback callback, int cookie) {
+        try {
+            AtomicInteger retval = new AtomicInteger(-1);
+            AtomicInteger handle = new AtomicInteger(0);
+            try {
+                as2_1().loadPhraseSoundModel_2_1(soundModel, new SoundTriggerCallback(callback),
+                        cookie,
+                        (r, h) -> {
+                            retval.set(r);
+                            handle.set(h);
+                        });
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                return loadPhraseSoundModel_2_0(soundModel, callback, cookie);
+            }
+            handleHalStatus(retval.get(), "loadSoundModel_2_1");
+            return handle.get();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void unloadSoundModel(int modelHandle) {
+        try {
+            int retval = as2_0().unloadSoundModel(modelHandle);
+            handleHalStatus(retval, "unloadSoundModel");
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void stopRecognition(int modelHandle) {
+        try {
+            int retval = as2_0().stopRecognition(modelHandle);
+            handleHalStatus(retval, "stopRecognition");
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+
+    }
+
+    @Override
+    public void stopAllRecognitions() {
+        try {
+            int retval = as2_0().stopAllRecognitions();
+            handleHalStatus(retval, "stopAllRecognitions");
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void startRecognition(int modelHandle,
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.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");
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                startRecognition_2_0(modelHandle, config, callback, cookie);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void getModelState(int modelHandle) {
+        try {
+            int retval = as2_2().getModelState(modelHandle);
+            handleHalStatus(retval, "getModelState");
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        } catch (NotSupported e) {
+            throw e.throwAsRecoverableException();
+        }
+    }
+
+    @Override
+    public int getModelParameter(int modelHandle, int param) {
+        AtomicInteger status = new AtomicInteger(-1);
+        AtomicInteger value = new AtomicInteger(0);
+        try {
+            as2_3().getParameter(modelHandle, param,
+                    (s, v) -> {
+                        status.set(s);
+                        value.set(v);
+                    });
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        } catch (NotSupported e) {
+            throw e.throwAsRecoverableException();
+        }
+        handleHalStatus(status.get(), "getParameter");
+        return value.get();
+    }
+
+    @Override
+    public void setModelParameter(int modelHandle, int param, int value) {
+        try {
+            int retval = as2_3().setParameter(modelHandle, param, value);
+            handleHalStatus(retval, "setParameter");
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        } catch (NotSupported e) {
+            throw e.throwAsRecoverableException();
+        }
+    }
+
+    @Override
+    public android.hardware.soundtrigger.V2_3.ModelParameterRange queryParameter(int modelHandle,
+            int param) {
+        AtomicInteger status = new AtomicInteger(-1);
+        AtomicReference<android.hardware.soundtrigger.V2_3.OptionalModelParameterRange>
+                optionalRange =
+                new AtomicReference<>();
+        try {
+            as2_3().queryParameter(modelHandle, param,
+                    (s, r) -> {
+                        status.set(s);
+                        optionalRange.set(r);
+                    });
+        } catch (NotSupported e) {
+            // For older drivers, we consider no model parameter to be supported.
+            return null;
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+        handleHalStatus(status.get(), "queryParameter");
+        return (optionalRange.get().getDiscriminator()
+                == android.hardware.soundtrigger.V2_3.OptionalModelParameterRange.hidl_discriminator.range)
+                ?
+                optionalRange.get().range() : null;
+    }
+
+    @Override
+    public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
+        return mBinder.linkToDeath(recipient, cookie);
+    }
+
+    @Override
+    public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
+        return mBinder.unlinkToDeath(recipient);
+    }
+
+    @Override
+    public String interfaceDescriptor() throws RemoteException {
+        return as2_0().interfaceDescriptor();
+    }
+
+    private int loadSoundModel_2_0(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+            Callback callback, int cookie)
+            throws RemoteException {
+        // Convert the soundModel to V2.0.
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 =
+                Hw2CompatUtil.convertSoundModel_2_1_to_2_0(soundModel);
+
+        AtomicInteger retval = new AtomicInteger(-1);
+        AtomicInteger handle = new AtomicInteger(0);
+        as2_0().loadSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie, (r, h) -> {
+            retval.set(r);
+            handle.set(h);
+        });
+        handleHalStatus(retval.get(), "loadSoundModel");
+        return handle.get();
+    }
+
+    private int loadPhraseSoundModel_2_0(
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+            Callback callback, int cookie)
+            throws RemoteException {
+        // Convert the soundModel to V2.0.
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+                Hw2CompatUtil.convertPhraseSoundModel_2_1_to_2_0(soundModel);
+
+        AtomicInteger retval = new AtomicInteger(-1);
+        AtomicInteger handle = new AtomicInteger(0);
+        as2_0().loadPhraseSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie,
+                (r, h) -> {
+                    retval.set(r);
+                    handle.set(h);
+                });
+        handleHalStatus(retval.get(), "loadSoundModel");
+        return handle.get();
+    }
+
+    private void startRecognition_2_0(int modelHandle,
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.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);
+        int retval = as2_0().startRecognition(modelHandle, config_2_0,
+                new SoundTriggerCallback(callback), cookie);
+        handleHalStatus(retval, "startRecognition");
+    }
+
+    private @NonNull
+    android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0() {
+        return mUnderlying_2_0;
+    }
+
+    private @NonNull
+    android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1() throws NotSupported {
+        if (mUnderlying_2_1 == null) {
+            throw new NotSupported("Underlying driver version < 2.1");
+        }
+        return mUnderlying_2_1;
+    }
+
+    private @NonNull
+    android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2() throws NotSupported {
+        if (mUnderlying_2_2 == null) {
+            throw new NotSupported("Underlying driver version < 2.2");
+        }
+        return mUnderlying_2_2;
+    }
+
+    private @NonNull
+    android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3() throws NotSupported {
+        if (mUnderlying_2_3 == null) {
+            throw new NotSupported("Underlying driver version < 2.3");
+        }
+        return mUnderlying_2_3;
+    }
+
+    /**
+     * A checked exception representing the requested interface version not being supported.
+     * At the public interface layer, use {@link #throwAsRecoverableException()} to propagate it to
+     * the caller if the request cannot be fulfilled.
+     */
+    private static class NotSupported extends Exception {
+        NotSupported(String message) {
+            super(message);
+        }
+
+        /**
+         * Throw this as a recoverable exception.
+         *
+         * @return Never actually returns anything. Always throws. Used so that caller can write
+         * throw e.throwAsRecoverableException().
+         */
+        RecoverableException throwAsRecoverableException() {
+            throw new RecoverableException(Status.OPERATION_NOT_SUPPORTED, getMessage());
+        }
+    }
+
+    private static class SoundTriggerCallback extends
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.Stub {
+        private final @NonNull
+        Callback mDelegate;
+
+        private SoundTriggerCallback(
+                @NonNull Callback delegate) {
+            mDelegate = Objects.requireNonNull(delegate);
+        }
+
+        @Override
+        public void recognitionCallback_2_1(
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+                int cookie) {
+            mDelegate.recognitionCallback(event, cookie);
+        }
+
+        @Override
+        public void phraseRecognitionCallback_2_1(
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+                int cookie) {
+            mDelegate.phraseRecognitionCallback(event, cookie);
+        }
+
+        @Override
+        public void soundModelCallback_2_1(
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent event,
+                int cookie) {
+            // Nobody cares.
+        }
+
+        @Override
+        public void recognitionCallback(
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event,
+                int cookie) {
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+                    Hw2CompatUtil.convertRecognitionEvent_2_0_to_2_1(event);
+            mDelegate.recognitionCallback(event_2_1, cookie);
+        }
+
+        @Override
+        public void phraseRecognitionCallback(
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+                int cookie) {
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+                    event_2_1 = Hw2CompatUtil.convertPhraseRecognitionEvent_2_0_to_2_1(event);
+            mDelegate.phraseRecognitionCallback(event_2_1, cookie);
+        }
+
+        @Override
+        public void soundModelCallback(
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent event,
+                int cookie) {
+            // Nobody cares.
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
new file mode 100644
index 0000000..9d51b65
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an implementation of the ISoundTriggerMiddlewareService interface.
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle invalid
+ * usage, and such usage will result in undefined behavior. If this service is to be offered to an
+ * untrusted client, it must be wrapped with input and state validation.
+ * <li>There is no binder instance associated with this implementation. Do not call asBinder().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status}
+ * constants. Any other exception thrown should be regarded as a bug in the implementation or one
+ * of its dependencies (assuming correct usage).
+ * <li>The implementation is designed for testibility by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies on
+ * Android runtime.
+ * <li>The implementation is thread-safe.
+ * </ul>
+ *
+ * @hide
+ */
+public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareService {
+    static private final String TAG = "SoundTriggerMiddlewareImpl";
+    private final SoundTriggerModule[] mModules;
+
+    /**
+     * Interface to the audio system, which can allocate capture session handles.
+     * SoundTrigger uses those sessions in order to associate a recognition session with an optional
+     * capture from the same device that triggered the recognition.
+     */
+    public static abstract class AudioSessionProvider {
+        public static final class AudioSession {
+            final int mSessionHandle;
+            final int mIoHandle;
+            final int mDeviceHandle;
+
+            AudioSession(int sessionHandle, int ioHandle, int deviceHandle) {
+                mSessionHandle = sessionHandle;
+                mIoHandle = ioHandle;
+                mDeviceHandle = deviceHandle;
+            }
+        }
+
+        public abstract AudioSession acquireSession();
+
+        public abstract void releaseSession(int sessionHandle);
+    }
+
+    /**
+     * Most generic constructor - gets an array of HAL driver instances.
+     */
+    public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+            @NonNull AudioSessionProvider audioSessionProvider) {
+        List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+
+        for (int i = 0; i < halServices.length; ++i) {
+            ISoundTriggerHw service = halServices[i];
+            try {
+                modules.add(new SoundTriggerModule(service, audioSessionProvider));
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+            }
+        }
+
+        mModules = modules.toArray(new SoundTriggerModule[modules.size()]);
+    }
+
+    /**
+     * Convenience constructor - gets a single HAL driver instance.
+     */
+    public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+            @NonNull AudioSessionProvider audioSessionProvider) {
+        this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+    }
+
+    @Override
+    public @NonNull
+    SoundTriggerModuleDescriptor[] listModules() {
+        SoundTriggerModuleDescriptor[] result = new SoundTriggerModuleDescriptor[mModules.length];
+
+        for (int i = 0; i < mModules.length; ++i) {
+            SoundTriggerModuleDescriptor desc = new SoundTriggerModuleDescriptor();
+            desc.handle = i;
+            desc.properties = mModules[i].getProperties();
+            result[i] = desc;
+        }
+        return result;
+    }
+
+    @Override
+    public @NonNull
+    ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+        return mModules[handle].attach(callback);
+    }
+
+    @Override
+    public void setExternalCaptureState(boolean active) {
+        for (SoundTriggerModule module : mModules) {
+            module.setExternalCaptureState(active);
+        }
+    }
+
+    @Override
+    public @NonNull
+    IBinder asBinder() {
+        throw new UnsupportedOperationException(
+                "This implementation is not inteded to be used directly with Binder.");
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
new file mode 100644
index 0000000..a7cfe10
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -0,0 +1,709 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.SystemService;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is a wrapper around an {@link ISoundTriggerMiddlewareService} implementation, which exposes
+ * it as a Binder service and enforces permissions and correct usage by the client, as well as makes
+ * sure that exceptions representing a server malfunction do not get sent to the client.
+ * <p>
+ * This is intended to extract the non-business logic out of the underlying implementation and thus
+ * make it easier to maintain each one of those separate aspects. A design trade-off is being made
+ * here, in that this class would need to essentially eavesdrop on all the client-server
+ * communication and retain all state known to the client, while the client doesn't necessarily care
+ * about all of it, and while the server has its own representation of this information. However,
+ * in this case, this is a small amount of data, and the benefits in code elegance seem worth it.
+ * There is also some additional cost in employing a simplistic locking mechanism here, but
+ * following the same line of reasoning, the benefits in code simplicity outweigh it.
+ * <p>
+ * Every public method in this class, overriding an interface method, must follow the following
+ * pattern:
+ * <code><pre>
+ * @Override public T method(S arg) {
+ *     // Permission check.
+ *     checkPermissions();
+ *     // Input validation.
+ *     ValidationUtil.validateS(arg);
+ *     synchronized (this) {
+ *         // State validation.
+ *         if (...state is not valid for this call...) {
+ *             throw new IllegalStateException("State is invalid because...");
+ *         }
+ *         // From here on, every exception isn't client's fault.
+ *         try {
+ *             T result = mDelegate.method(arg);
+ *             // Update state.;
+ *             ...
+ *             return result;
+ *         } catch (Exception e) {
+ *             throw handleException(e);
+ *         }
+ *     }
+ * }
+ * </pre></code>
+ * Following this patterns ensures a consistent and rigorous handling of all aspects associated
+ * with client-server separation.
+ * <p>
+ * <b>Exception handling approach:</b><br>
+ * We make sure all client faults (permissions, argument and state validation) happen first, and
+ * would throw {@link SecurityException}, {@link IllegalArgumentException}/
+ * {@link NullPointerException} or {@link
+ * IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
+ * will get sent back to the client.<br>
+ * Once this is done, any subsequent fault is considered a server fault. Only {@link
+ * RecoverableException}s thrown by the implementation are special-cased: they would get sent back
+ * to the caller as a {@link ServiceSpecificException}, which is the behavior of Binder. Any other
+ * exception gets wrapped with a {@link InternalServerError}, which is specifically chosen as a type
+ * that <b>does NOT</b> get forwarded by binder. Those exceptions would be handled by a high-level
+ * exception handler on the server side, typically resulting in rebooting the server.
+ * <p>
+ * <b>Exposing this service as a System Service:</b><br>
+ * Insert this line into {@link com.android.server.SystemServer}:
+ * <code><pre>
+ * mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+ * </pre></code>
+ *
+ * {@hide}
+ */
+public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
+    static private final String TAG = "SoundTriggerMiddlewareService";
+
+    final ISoundTriggerMiddlewareService mDelegate;
+    final Context mContext;
+    Set<Integer> mModuleHandles;
+
+    /**
+     * Constructor for internal use only. Could be exposed for testing purposes in the future.
+     * Users should access this class via {@link Lifecycle}.
+     */
+    private SoundTriggerMiddlewareService(
+            @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
+        mDelegate = delegate;
+        mContext = context;
+    }
+
+    /**
+     * Generic exception handling for exceptions thrown by the underlying implementation.
+     *
+     * Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
+     * by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
+     * (<b>not</b> passed by Binder to the caller).
+     * <p>
+     * Typical usage:
+     * <code><pre>
+     * try {
+     *     ... Do server operations ...
+     * } catch (Exception e) {
+     *     throw handleException(e);
+     * }
+     * </pre></code>
+     */
+    private static @NonNull
+    RuntimeException handleException(@NonNull Exception e) {
+        if (e instanceof RecoverableException) {
+            throw new ServiceSpecificException(((RecoverableException) e).errorCode,
+                    e.getMessage());
+        }
+        throw new InternalServerError(e);
+    }
+
+    @Override
+    public @NonNull
+    SoundTriggerModuleDescriptor[] listModules() {
+        // Permission check.
+        checkPermissions();
+        // Input validation (always valid).
+
+        synchronized (this) {
+            // State validation (always valid).
+
+            // From here on, every exception isn't client's fault.
+            try {
+                SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
+                mModuleHandles = new HashSet<>(result.length);
+                for (SoundTriggerModuleDescriptor desc : result) {
+                    mModuleHandles.add(desc.handle);
+                }
+                return result;
+            } catch (Exception e) {
+                throw handleException(e);
+            }
+        }
+    }
+
+    @Override
+    public @NonNull
+    ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+        // Permission check.
+        checkPermissions();
+        // Input validation.
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(callback.asBinder());
+
+        synchronized (this) {
+            // State validation.
+            if (mModuleHandles == null) {
+                throw new IllegalStateException(
+                        "Client must call listModules() prior to attaching.");
+            }
+            if (!mModuleHandles.contains(handle)) {
+                throw new IllegalArgumentException("Invalid handle: " + handle);
+            }
+
+            // From here on, every exception isn't client's fault.
+            try {
+                ModuleService moduleService = new ModuleService(callback);
+                moduleService.attach(mDelegate.attach(handle, moduleService));
+                return moduleService;
+            } catch (Exception e) {
+                throw handleException(e);
+            }
+        }
+    }
+
+    @Override
+    public void setExternalCaptureState(boolean active) {
+        // Permission check.
+        checkPreemptPermissions();
+        // Input validation (always valid).
+
+        synchronized (this) {
+            // State validation (always valid).
+
+            // From here on, every exception isn't client's fault.
+            try {
+                mDelegate.setExternalCaptureState(active);
+            } catch (Exception e) {
+                throw handleException(e);
+            }
+        }
+    }
+
+    /**
+     * Throws a {@link SecurityException} if caller 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.");
+    }
+
+    /**
+     * Throws a {@link SecurityException} if caller 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.");
+    }
+
+    /** State of a sound model. */
+    static class ModelState {
+        /** Activity state of a sound model. */
+        enum Activity {
+            /** Model is loaded, recognition is inactive. */
+            LOADED,
+            /** Model is loaded, recognition is active. */
+            ACTIVE
+        }
+
+        /** Activity state. */
+        public Activity activityState = Activity.LOADED;
+
+        /**
+         * A map of known parameter support. A missing key means we don't know yet whether the
+         * parameter is supported. A null value means it is known to not be supported. A non-null
+         * value indicates the valid value range.
+         */
+        private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
+
+        /**
+         * Check that the given parameter is known to be supported for this model.
+         *
+         * @param modelParam The parameter key.
+         */
+        public void checkSupported(int modelParam) {
+            if (!parameterSupport.containsKey(modelParam)) {
+                throw new IllegalStateException("Parameter has not been checked for support.");
+            }
+            ModelParameterRange range = parameterSupport.get(modelParam);
+            if (range == null) {
+                throw new IllegalArgumentException("Paramater is not supported.");
+            }
+        }
+
+        /**
+         * Check that the given parameter is known to be supported for this model and that the given
+         * value is a valid value for it.
+         *
+         * @param modelParam The parameter key.
+         * @param value      The value.
+         */
+        public void checkSupported(int modelParam, int value) {
+            if (!parameterSupport.containsKey(modelParam)) {
+                throw new IllegalStateException("Parameter has not been checked for support.");
+            }
+            ModelParameterRange range = parameterSupport.get(modelParam);
+            if (range == null) {
+                throw new IllegalArgumentException("Paramater is not supported.");
+            }
+            Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
+                    "value");
+        }
+
+        /**
+         * Update support state for the given parameter for this model.
+         *
+         * @param modelParam The parameter key.
+         * @param range      The parameter value range, or null if not supported.
+         */
+        public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+            parameterSupport.put(modelParam, range);
+        }
+    }
+
+    /**
+     * Entry-point to this module: exposes the module as a {@link SystemService}.
+     */
+    public static final class Lifecycle extends SystemService {
+        private SoundTriggerMiddlewareService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            ISoundTriggerHw[] services;
+            try {
+                services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)};
+                Log.d(TAG, "Connected to default ISoundTriggerHw");
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e);
+                services = new ISoundTriggerHw[0];
+            }
+
+            mService = new SoundTriggerMiddlewareService(
+                    new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
+                    getContext());
+            publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+        }
+    }
+
+    /**
+     * A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
+     * mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
+     */
+    private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
+            DeathRecipient {
+        private final ISoundTriggerCallback mCallback;
+        private ISoundTriggerModule mDelegate;
+        private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+
+        ModuleService(@NonNull ISoundTriggerCallback callback) {
+            mCallback = callback;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+
+        void attach(@NonNull ISoundTriggerModule delegate) {
+            mDelegate = delegate;
+        }
+
+        @Override
+        public int loadModel(@NonNull SoundModel model) {
+            // Permission check.
+            checkPermissions();
+            // Input validation.
+            ValidationUtil.validateGenericModel(model);
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has been detached.");
+                }
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    int handle = mDelegate.loadModel(model);
+                    mLoadedModels.put(handle, new ModelState());
+                    return handle;
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        @Override
+        public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+            // Permission check.
+            checkPermissions();
+            // Input validation.
+            ValidationUtil.validatePhraseModel(model);
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has been detached.");
+                }
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    int handle = mDelegate.loadPhraseModel(model);
+                    mLoadedModels.put(handle, new ModelState());
+                    return handle;
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        @Override
+        public void unloadModel(int modelHandle) {
+            // Permission check.
+            checkPermissions();
+            // Input validation (always valid).
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has been detached.");
+                }
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState == null) {
+                    throw new IllegalStateException("Invalid handle: " + modelHandle);
+                }
+                if (modelState.activityState != ModelState.Activity.LOADED) {
+                    throw new IllegalStateException("Model with handle: " + modelHandle
+                            + " has invalid state for unloading: " + modelState.activityState);
+                }
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    mDelegate.unloadModel(modelHandle);
+                    mLoadedModels.remove(modelHandle);
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        @Override
+        public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+            // Permission check.
+            checkPermissions();
+            // Input validation.
+            ValidationUtil.validateRecognitionConfig(config);
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has been detached.");
+                }
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState == null) {
+                    throw new IllegalStateException("Invalid handle: " + modelHandle);
+                }
+                if (modelState.activityState != ModelState.Activity.LOADED) {
+                    throw new IllegalStateException("Model with handle: " + modelHandle
+                            + " has invalid state for starting recognition: "
+                            + modelState.activityState);
+                }
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    mDelegate.startRecognition(modelHandle, config);
+                    modelState.activityState = ModelState.Activity.ACTIVE;
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        @Override
+        public void stopRecognition(int modelHandle) {
+            // Permission check.
+            checkPermissions();
+            // Input validation (always valid).
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has been detached.");
+                }
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState == null) {
+                    throw new IllegalStateException("Invalid handle: " + modelHandle);
+                }
+                // stopRecognition is idempotent - no need to check model state.
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    mDelegate.stopRecognition(modelHandle);
+                    modelState.activityState = ModelState.Activity.LOADED;
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        @Override
+        public void forceRecognitionEvent(int modelHandle) {
+            // Permission check.
+            checkPermissions();
+            // Input validation (always valid).
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has been detached.");
+                }
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState == null) {
+                    throw new IllegalStateException("Invalid handle: " + modelHandle);
+                }
+                // forceRecognitionEvent is idempotent - no need to check model state.
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    mDelegate.forceRecognitionEvent(modelHandle);
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        @Override
+        public void setModelParameter(int modelHandle, int modelParam, int value) {
+            // Permission check.
+            checkPermissions();
+            // Input validation.
+            ValidationUtil.validateModelParameter(modelParam);
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has been detached.");
+                }
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState == null) {
+                    throw new IllegalStateException("Invalid handle: " + modelHandle);
+                }
+                modelState.checkSupported(modelParam, value);
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    mDelegate.setModelParameter(modelHandle, modelParam, value);
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        @Override
+        public int getModelParameter(int modelHandle, int modelParam) {
+            // Permission check.
+            checkPermissions();
+            // Input validation.
+            ValidationUtil.validateModelParameter(modelParam);
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has been detached.");
+                }
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState == null) {
+                    throw new IllegalStateException("Invalid handle: " + modelHandle);
+                }
+                modelState.checkSupported(modelParam);
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    return mDelegate.getModelParameter(modelHandle, modelParam);
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        @Override
+        @Nullable
+        public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+            // Permission check.
+            checkPermissions();
+            // Input validation.
+            ValidationUtil.validateModelParameter(modelParam);
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has been detached.");
+                }
+                ModelState modelState = mLoadedModels.get(modelHandle);
+                if (modelState == null) {
+                    throw new IllegalStateException("Invalid handle: " + modelHandle);
+                }
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
+                            modelParam);
+                    modelState.updateParameterSupport(modelParam, result);
+                    return result;
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        @Override
+        public void detach() {
+            // Permission check.
+            checkPermissions();
+            // Input validation (always valid).
+
+            synchronized (this) {
+                // State validation.
+                if (mDelegate == null) {
+                    throw new IllegalStateException("Module has already been detached.");
+                }
+                if (!mLoadedModels.isEmpty()) {
+                    throw new IllegalStateException("Cannot detach while models are loaded.");
+                }
+
+                // From here on, every exception isn't client's fault.
+                try {
+                    detachInternal();
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+
+        private void detachInternal() {
+            try {
+                mDelegate.detach();
+                mDelegate = null;
+                mCallback.asBinder().unlinkToDeath(this, 0);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        }
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // Callbacks
+
+        @Override
+        public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
+            synchronized (this) {
+                if (event.status != RecognitionStatus.FORCED) {
+                    mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+                }
+                try {
+                    mCallback.onRecognition(modelHandle, event);
+                } catch (RemoteException e) {
+                    // Dead client will be handled by binderDied() - no need to handle here.
+                    // In any case, client callbacks are considered best effort.
+                    Log.e(TAG, "Client callback execption.", e);
+                }
+            }
+        }
+
+        @Override
+        public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
+            synchronized (this) {
+                if (event.common.status != RecognitionStatus.FORCED) {
+                    mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+                }
+                try {
+                    mCallback.onPhraseRecognition(modelHandle, event);
+                } catch (RemoteException e) {
+                    // Dead client will be handled by binderDied() - no need to handle here.
+                    // In any case, client callbacks are considered best effort.
+                    Log.e(TAG, "Client callback execption.", e);
+                }
+            }
+        }
+
+        @Override
+        public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+            synchronized (this) {
+                try {
+                    mCallback.onRecognitionAvailabilityChange(available);
+                } catch (RemoteException e) {
+                    // Dead client will be handled by binderDied() - no need to handle here.
+                    // In any case, client callbacks are considered best effort.
+                    Log.e(TAG, "Client callback execption.", e);
+                }
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            // This is called whenever our client process dies.
+            synchronized (this) {
+                try {
+                    // Gracefully stop all active recognitions and unload the models.
+                    for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
+                        if (entry.getValue().activityState == ModelState.Activity.ACTIVE) {
+                            mDelegate.stopRecognition(entry.getKey());
+                        }
+                        mDelegate.unloadModel(entry.getKey());
+                    }
+                    // Detach.
+                    detachInternal();
+                } catch (Exception e) {
+                    throw handleException(e);
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
new file mode 100644
index 0000000..81789e1
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -0,0 +1,545 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_2.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is an implementation of a single module of the ISoundTriggerMiddlewareService interface,
+ * exposing itself through the {@link ISoundTriggerModule} interface, possibly to multiple separate
+ * clients.
+ * <p>
+ * Typical usage is to query the module capabilities using {@link #getProperties()} and then to use
+ * the module through an {@link ISoundTriggerModule} instance, obtained via {@link
+ * #attach(ISoundTriggerCallback)}. Every such interface is its own session and state is not shared
+ * between sessions (i.e. cannot use a handle obtained from one session through another).
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle
+ * invalid usage, and such usage will result in undefined behavior. If this service is to be
+ * offered to an untrusted client, it must be wrapped with input and state validation.
+ * <li>The underlying driver is assumed to be correct. This implementation does not attempt to
+ * gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
+ * service is to used with an untrusted driver, the driver must be wrapped with validation / error
+ * recovery code.
+ * <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
+ * considered recoverable faults and should not occur in a properly functioning system.
+ * <li>There is no binder instance associated with this implementation. Do not call asBinder().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status} constants. Any other exception
+ * thrown should be regarded as a bug in the implementation or one of its dependencies
+ * (assuming correct usage).
+ * <li>The implementation is designed for testability by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies
+ * on Android runtime.
+ * <li>The implementation is thread-safe. This is achieved by a simplistic model, where all entry-
+ * points (both client API and driver callbacks) obtain a lock on the SoundTriggerModule instance
+ * for their entire scope. Any other method can be assumed to be running with the lock already
+ * obtained, so no further locking should be done. While this is not necessarily the most efficient
+ * synchronization strategy, it is very easy to reason about and this code is likely not on any
+ * performance-critical
+ * path.
+ * </ul>
+ *
+ * @hide
+ */
+class SoundTriggerModule {
+    static private final String TAG = "SoundTriggerModule";
+    @NonNull private final ISoundTriggerHw2 mHalService;
+    @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
+    private final Set<Session> mActiveSessions = new HashSet<>();
+    private int mNumLoadedModels = 0;
+    private SoundTriggerModuleProperties mProperties = null;
+    private boolean mRecognitionAvailable;
+
+    /**
+     * Ctor.
+     *
+     * @param halService The underlying HAL driver.
+     */
+    SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+            @NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
+        assert halService != null;
+        mHalService = new SoundTriggerHw2Compat(halService);
+        mAudioSessionProvider = audioSessionProvider;
+        mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
+
+        // We conservatively assume that external capture is active until explicitly told otherwise.
+        mRecognitionAvailable = mProperties.concurrentCapture;
+    }
+
+    /**
+     * Establish a client session with this module.
+     *
+     * This module may be shared by multiple clients, each will get its own session. While resources
+     * are shared between the clients, each session has its own state and data should not be shared
+     * across sessions.
+     *
+     * @param callback The client callback, which will be used for all messages. This is a oneway
+     *                 callback, so will never block, throw an unchecked exception or return a
+     *                 value.
+     * @return The interface through which this module can be controlled.
+     */
+    synchronized @NonNull
+    Session attach(@NonNull ISoundTriggerCallback callback) {
+        Log.d(TAG, "attach()");
+        Session session = new Session(callback);
+        mActiveSessions.add(session);
+        return session;
+    }
+
+    /**
+     * Query the module's properties.
+     *
+     * @return The properties structure.
+     */
+    synchronized @NonNull
+    SoundTriggerModuleProperties getProperties() {
+        return mProperties;
+    }
+
+    /**
+     * Notify the module that external capture has started / finished, using the same input device
+     * used for recognition.
+     * If the underlying driver does not support recognition while capturing, capture will be
+     * aborted, and the recognition callback will receive and abort event. In addition, all active
+     * clients will be notified of the change in state.
+     *
+     * @param active true iff external capture is active.
+     */
+    synchronized void setExternalCaptureState(boolean active) {
+        Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
+        if (mProperties.concurrentCapture) {
+            // If we support concurrent capture, we don't care about any of this.
+            return;
+        }
+        mRecognitionAvailable = !active;
+        if (!mRecognitionAvailable) {
+            // Our module does not support recognition while a capture is active -
+            // need to abort all active recognitions.
+            for (Session session : mActiveSessions) {
+                session.abortActiveRecognitions();
+            }
+        }
+        for (Session session : mActiveSessions) {
+            session.notifyRecognitionAvailability();
+        }
+    }
+
+    /**
+     * Remove session from the list of active sessions.
+     *
+     * @param session The session to remove.
+     */
+    private void removeSession(@NonNull Session session) {
+        mActiveSessions.remove(session);
+    }
+
+    /** State of a single sound model. */
+    private enum ModelState {
+        /** Initial state, until load() is called. */
+        INIT,
+        /** Model is loaded, but recognition is not active. */
+        LOADED,
+        /** Model is loaded and recognition is active. */
+        ACTIVE
+    }
+
+    /**
+     * A single client session with this module.
+     *
+     * This is the main interface used to interact with this module.
+     */
+    private class Session implements ISoundTriggerModule {
+        private ISoundTriggerCallback mCallback;
+        private Map<Integer, Model> mLoadedModels = new HashMap<>();
+
+        /**
+         * Ctor.
+         *
+         * @param callback The client callback interface.
+         */
+        private Session(@NonNull ISoundTriggerCallback callback) {
+            mCallback = callback;
+            notifyRecognitionAvailability();
+        }
+
+        @Override
+        public void detach() {
+            Log.d(TAG, "detach()");
+            synchronized (SoundTriggerModule.this) {
+                removeSession(this);
+            }
+        }
+
+        @Override
+        public int loadModel(@NonNull SoundModel model) {
+            Log.d(TAG, String.format("loadModel(model=%s)", model));
+            synchronized (SoundTriggerModule.this) {
+                if (mNumLoadedModels == mProperties.maxSoundModels) {
+                    throw new RecoverableException(Status.RESOURCE_CONTENTION,
+                            "Maximum number of models loaded.");
+                }
+                Model loadedModel = new Model();
+                int result = loadedModel.load(model);
+                ++mNumLoadedModels;
+                return result;
+            }
+        }
+
+        @Override
+        public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+            Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
+            synchronized (SoundTriggerModule.this) {
+                if (mNumLoadedModels == mProperties.maxSoundModels) {
+                    throw new RecoverableException(Status.RESOURCE_CONTENTION,
+                            "Maximum number of models loaded.");
+                }
+                Model loadedModel = new Model();
+                int result = loadedModel.load(model);
+                ++mNumLoadedModels;
+                Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+                return result;
+            }
+        }
+
+        @Override
+        public void unloadModel(int modelHandle) {
+            Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
+            synchronized (SoundTriggerModule.this) {
+                mLoadedModels.get(modelHandle).unload();
+                --mNumLoadedModels;
+            }
+        }
+
+        @Override
+        public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+            Log.d(TAG,
+                    String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
+            synchronized (SoundTriggerModule.this) {
+                mLoadedModels.get(modelHandle).startRecognition(config);
+            }
+        }
+
+        @Override
+        public void stopRecognition(int modelHandle) {
+            Log.d(TAG, String.format("stopRecognition(handle=%d)", modelHandle));
+            synchronized (SoundTriggerModule.this) {
+                mLoadedModels.get(modelHandle).stopRecognition();
+            }
+        }
+
+        @Override
+        public void forceRecognitionEvent(int modelHandle) {
+            Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
+            synchronized (SoundTriggerModule.this) {
+                mLoadedModels.get(modelHandle).forceRecognitionEvent();
+            }
+        }
+
+        @Override
+        public void setModelParameter(int modelHandle, int modelParam, int value)
+                throws RemoteException {
+            Log.d(TAG,
+                    String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
+                            modelParam, value));
+            synchronized (SoundTriggerModule.this) {
+                mLoadedModels.get(modelHandle).setParameter(modelParam, value);
+            }
+        }
+
+        @Override
+        public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+            Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
+                    modelParam));
+            synchronized (SoundTriggerModule.this) {
+                return mLoadedModels.get(modelHandle).getParameter(modelParam);
+            }
+        }
+
+        @Override
+        @Nullable
+        public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+            Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
+                    modelParam));
+            synchronized (SoundTriggerModule.this) {
+                return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
+            }
+        }
+
+        /**
+         * Abort all currently active recognitions.
+         */
+        private void abortActiveRecognitions() {
+            for (Model model : mLoadedModels.values()) {
+                model.abortActiveRecognition();
+            }
+        }
+
+        private void notifyRecognitionAvailability() {
+            try {
+                mCallback.onRecognitionAvailabilityChange(mRecognitionAvailable);
+            } catch (RemoteException e) {
+                // Dead client will be handled by binderDied() - no need to handle here.
+                // In any case, client callbacks are considered best effort.
+                Log.e(TAG, "Client callback execption.", e);
+            }
+        }
+
+        @Override
+        public @NonNull
+        IBinder asBinder() {
+            throw new UnsupportedOperationException(
+                    "This implementation is not intended to be used directly with Binder.");
+        }
+
+        /**
+         * A single sound model in the system.
+         *
+         * All model-based operations are delegated to this class and implemented here.
+         */
+        private class Model implements ISoundTriggerHw2.Callback {
+            public int mHandle;
+            private ModelState mState = ModelState.INIT;
+            private int mModelType = SoundModelType.UNKNOWN;
+            private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession mSession;
+
+            private @NonNull
+            ModelState getState() {
+                return mState;
+            }
+
+            private void setState(@NonNull ModelState state) {
+                mState = state;
+                SoundTriggerModule.this.notifyAll();
+            }
+
+            private void waitStateChange() throws InterruptedException {
+                SoundTriggerModule.this.wait();
+            }
+
+            private int load(@NonNull SoundModel model) {
+                mModelType = model.type;
+                ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
+
+                mSession = mAudioSessionProvider.acquireSession();
+                try {
+                    mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
+                } catch (Exception e) {
+                    mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+                    throw e;
+                }
+
+                setState(ModelState.LOADED);
+                mLoadedModels.put(mHandle, this);
+                return mHandle;
+            }
+
+            private int load(@NonNull PhraseSoundModel model) {
+                mModelType = model.common.type;
+                ISoundTriggerHw.PhraseSoundModel hidlModel =
+                        ConversionUtil.aidl2hidlPhraseSoundModel(model);
+
+                mSession = mAudioSessionProvider.acquireSession();
+                try {
+                    mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
+                } catch (Exception e) {
+                    mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+                    throw e;
+                }
+
+                setState(ModelState.LOADED);
+                mLoadedModels.put(mHandle, this);
+                return mHandle;
+            }
+
+            private void unload() {
+                mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+                mHalService.unloadSoundModel(mHandle);
+                mLoadedModels.remove(mHandle);
+            }
+
+            private void startRecognition(@NonNull RecognitionConfig config) {
+                if (!mRecognitionAvailable) {
+                    // Recognition is unavailable - send an abort event immediately.
+                    notifyAbort();
+                    return;
+                }
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+                        ConversionUtil.aidl2hidlRecognitionConfig(config);
+                hidlConfig.header.captureDevice = mSession.mDeviceHandle;
+                hidlConfig.header.captureHandle = mSession.mIoHandle;
+                mHalService.startRecognition(mHandle, hidlConfig, this, 0);
+                setState(ModelState.ACTIVE);
+            }
+
+            private void stopRecognition() {
+                if (getState() == ModelState.LOADED) {
+                    // This call is idempotent in order to avoid races.
+                    return;
+                }
+                mHalService.stopRecognition(mHandle);
+                setState(ModelState.LOADED);
+            }
+
+            /** Request a forced recognition event. Will do nothing if recognition is inactive. */
+            private void forceRecognitionEvent() {
+                if (getState() != ModelState.ACTIVE) {
+                    // This call is idempotent in order to avoid races.
+                    return;
+                }
+                mHalService.getModelState(mHandle);
+            }
+
+
+            private void setParameter(int modelParam, int value) {
+                mHalService.setModelParameter(mHandle,
+                        ConversionUtil.aidl2hidlModelParameter(modelParam), value);
+            }
+
+            private int getParameter(int modelParam) {
+                return mHalService.getModelParameter(mHandle,
+                        ConversionUtil.aidl2hidlModelParameter(modelParam));
+            }
+
+            @Nullable
+            private ModelParameterRange queryModelParameterSupport(int modelParam) {
+                return ConversionUtil.hidl2aidlModelParameterRange(
+                        mHalService.queryParameter(mHandle,
+                                ConversionUtil.aidl2hidlModelParameter(modelParam)));
+            }
+
+            /** Abort the recognition, if active. */
+            private void abortActiveRecognition() {
+                // If we're inactive, do nothing.
+                if (getState() != ModelState.ACTIVE) {
+                    return;
+                }
+                // Stop recognition.
+                stopRecognition();
+
+                // Notify the client that recognition has been aborted.
+                notifyAbort();
+            }
+
+            /** Notify the client that recognition has been aborted. */
+            private void notifyAbort() {
+                try {
+                    switch (mModelType) {
+                        case SoundModelType.GENERIC: {
+                            android.media.soundtrigger_middleware.RecognitionEvent event =
+                                    new android.media.soundtrigger_middleware.RecognitionEvent();
+                            event.status =
+                                    android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+                            mCallback.onRecognition(mHandle, event);
+                        }
+                        break;
+
+                        case SoundModelType.KEYPHRASE: {
+                            android.media.soundtrigger_middleware.PhraseRecognitionEvent event =
+                                    new android.media.soundtrigger_middleware.PhraseRecognitionEvent();
+                            event.common =
+                                    new android.media.soundtrigger_middleware.RecognitionEvent();
+                            event.common.status =
+                                    android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+                            mCallback.onPhraseRecognition(mHandle, event);
+                        }
+                        break;
+
+                        default:
+                            Log.e(TAG, "Unknown model type: " + mModelType);
+
+                    }
+                } catch (RemoteException e) {
+                    // Dead client will be handled by binderDied() - no need to handle here.
+                    // In any case, client callbacks are considered best effort.
+                    Log.e(TAG, "Client callback execption.", e);
+                }
+            }
+
+            @Override
+            public void recognitionCallback(
+                    @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
+                    int cookie) {
+                Log.d(TAG, String.format("recognitionCallback_2_1(event=%s, cookie=%d)",
+                        recognitionEvent, cookie));
+                synchronized (SoundTriggerModule.this) {
+                    android.media.soundtrigger_middleware.RecognitionEvent aidlEvent =
+                            ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
+                    aidlEvent.captureSession = mSession.mSessionHandle;
+                    try {
+                        mCallback.onRecognition(mHandle, aidlEvent);
+                    } catch (RemoteException e) {
+                        // Dead client will be handled by binderDied() - no need to handle here.
+                        // In any case, client callbacks are considered best effort.
+                        Log.e(TAG, "Client callback execption.", e);
+                    }
+                    if (aidlEvent.status
+                            != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+                        setState(ModelState.LOADED);
+                    }
+                }
+            }
+
+            @Override
+            public void phraseRecognitionCallback(
+                    @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
+                    int cookie) {
+                Log.d(TAG, String.format("phraseRecognitionCallback_2_1(event=%s, cookie=%d)",
+                        phraseRecognitionEvent, cookie));
+                synchronized (SoundTriggerModule.this) {
+                    android.media.soundtrigger_middleware.PhraseRecognitionEvent aidlEvent =
+                            ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
+                    aidlEvent.common.captureSession = mSession.mSessionHandle;
+                    try {
+                        mCallback.onPhraseRecognition(mHandle, aidlEvent);
+                    } catch (RemoteException e) {
+                        // Dead client will be handled by binderDied() - no need to handle here.
+                        // In any case, client callbacks are considered best effort.
+                        Log.e(TAG, "Client callback execption.", e);
+                    }
+                    if (aidlEvent.common.status
+                            != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+                        setState(ModelState.LOADED);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
new file mode 100644
index 0000000..9ed894b
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.soundtrigger_middleware"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
new file mode 100644
index 0000000..80f69d0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for representing UUIDs as strings.
+ *
+ * @hide
+ */
+public class UuidUtil {
+    /**
+     * Regex pattern that can be used to validate / extract the various fields of a string-formatted
+     * UUID.
+     */
+    static final Pattern PATTERN = Pattern.compile("^([a-fA-F0-9]{8})-" +
+            "([a-fA-F0-9]{4})-" +
+            "([a-fA-F0-9]{4})-" +
+            "([a-fA-F0-9]{4})-" +
+            "([a-fA-F0-9]{2})" +
+            "([a-fA-F0-9]{2})" +
+            "([a-fA-F0-9]{2})" +
+            "([a-fA-F0-9]{2})" +
+            "([a-fA-F0-9]{2})" +
+            "([a-fA-F0-9]{2})$");
+
+    /** Printf-style pattern for formatting the various fields of a UUID as a string. */
+    static final String FORMAT = "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x";
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
new file mode 100644
index 0000000..4898e6b
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
@@ -0,0 +1,117 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for asserting the validity of various data types used by this module.
+ * Each of the methods below would throw an {@link IllegalArgumentException} if its input is
+ * invalid. The input's validity is determined irrespective of any context. In cases where the valid
+ * value space is further limited by state, it is the caller's responsibility to assert.
+ *
+ * @hide
+ */
+public class ValidationUtil {
+    static void validateUuid(@Nullable String uuid) {
+        Preconditions.checkNotNull(uuid);
+        Matcher matcher = UuidUtil.PATTERN.matcher(uuid);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException(
+                    "Illegal format for UUID: " + uuid);
+        }
+    }
+
+    static void validateGenericModel(@Nullable SoundModel model) {
+        validateModel(model, SoundModelType.GENERIC);
+    }
+
+    static void validateModel(@Nullable SoundModel model, int expectedType) {
+        Preconditions.checkNotNull(model);
+        if (model.type != expectedType) {
+            throw new IllegalArgumentException("Invalid type");
+        }
+        validateUuid(model.uuid);
+        validateUuid(model.vendorUuid);
+        Preconditions.checkNotNull(model.data);
+    }
+
+    static void validatePhraseModel(@Nullable PhraseSoundModel model) {
+        Preconditions.checkNotNull(model);
+        validateModel(model.common, SoundModelType.KEYPHRASE);
+        Preconditions.checkNotNull(model.phrases);
+        for (Phrase phrase : model.phrases) {
+            Preconditions.checkNotNull(phrase);
+            if ((phrase.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+                    | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+                    | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+                throw new IllegalArgumentException("Invalid recognitionModes");
+            }
+            Preconditions.checkNotNull(phrase.users);
+            Preconditions.checkNotNull(phrase.locale);
+            Preconditions.checkNotNull(phrase.text);
+        }
+    }
+
+    static void validateRecognitionConfig(@Nullable RecognitionConfig config) {
+        Preconditions.checkNotNull(config);
+        Preconditions.checkNotNull(config.phraseRecognitionExtras);
+        for (PhraseRecognitionExtra extra : config.phraseRecognitionExtras) {
+            Preconditions.checkNotNull(extra);
+            if ((extra.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+                    | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+                    | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+                throw new IllegalArgumentException("Invalid recognitionModes");
+            }
+            if (extra.confidenceLevel < 0 || extra.confidenceLevel > 100) {
+                throw new IllegalArgumentException("Invalid confidenceLevel");
+            }
+            Preconditions.checkNotNull(extra.levels);
+            for (ConfidenceLevel level : extra.levels) {
+                Preconditions.checkNotNull(level);
+                if (level.levelPercent < 0 || level.levelPercent > 100) {
+                    throw new IllegalArgumentException("Invalid confidenceLevel");
+                }
+            }
+        }
+        Preconditions.checkNotNull(config.data);
+    }
+
+    static void validateModelParameter(int modelParam) {
+        switch (modelParam) {
+            case ModelParameter.THRESHOLD_FACTOR:
+                return;
+
+            default:
+                throw new IllegalArgumentException("Invalid model parameter");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index b4d8053..b3013c7 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -385,7 +385,7 @@
     }
 
     private static boolean isOriginAutomatic(@Origin int origin) {
-        return origin == ORIGIN_PHONE;
+        return origin != ORIGIN_MANUAL;
     }
 
     @GuardedBy("this")
@@ -456,15 +456,17 @@
      * Dumps internal state such as field values.
      */
     public synchronized void dumpState(PrintWriter pw, String[] args) {
-        pw.println("TimeZoneDetectorStrategy:");
-        pw.println("mCallback.isTimeZoneDetectionEnabled()="
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+        ipw.println("TimeZoneDetectorStrategy:");
+
+        ipw.increaseIndent(); // level 1
+        ipw.println("mCallback.isTimeZoneDetectionEnabled()="
                 + mCallback.isAutoTimeZoneDetectionEnabled());
-        pw.println("mCallback.isDeviceTimeZoneInitialized()="
+        ipw.println("mCallback.isDeviceTimeZoneInitialized()="
                 + mCallback.isDeviceTimeZoneInitialized());
-        pw.println("mCallback.getDeviceTimeZone()="
+        ipw.println("mCallback.getDeviceTimeZone()="
                 + mCallback.getDeviceTimeZone());
 
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
         ipw.println("Time zone change log:");
         ipw.increaseIndent(); // level 2
         mTimeZoneChangesLog.dump(ipw);
@@ -485,8 +487,6 @@
         ipw.decreaseIndent(); // level 2
         ipw.decreaseIndent(); // level 1
         ipw.flush();
-
-        pw.flush();
     }
 
     /**
diff --git a/services/core/java/com/android/server/utils/TEST_MAPPING b/services/core/java/com/android/server/utils/TEST_MAPPING
new file mode 100644
index 0000000..bb7cea9
--- /dev/null
+++ b/services/core/java/com/android/server/utils/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.utils"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/utils/quota/UptcMap.java b/services/core/java/com/android/server/utils/quota/UptcMap.java
new file mode 100644
index 0000000..7b49913
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/UptcMap.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.utils.quota;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.SparseArrayMap;
+
+import java.util.function.Consumer;
+
+/**
+ * A SparseArrayMap of ArrayMaps, which is suitable for holding userId-packageName-tag combination
+ * (UPTC)->object associations. Tags are any desired String.
+ *
+ * @see Uptc
+ */
+class UptcMap<T> {
+    private final SparseArrayMap<ArrayMap<String, T>> mData = new SparseArrayMap<>();
+
+    public void add(int userId, @NonNull String packageName, @Nullable String tag,
+            @Nullable T obj) {
+        ArrayMap<String, T> data = mData.get(userId, packageName);
+        if (data == null) {
+            data = new ArrayMap<>();
+            mData.add(userId, packageName, data);
+        }
+        data.put(tag, obj);
+    }
+
+    public void clear() {
+        mData.clear();
+    }
+
+    public boolean contains(int userId, @NonNull String packageName) {
+        return mData.contains(userId, packageName);
+    }
+
+    public boolean contains(int userId, @NonNull String packageName, @Nullable String tag) {
+        // This structure never inserts a null ArrayMap, so if get(userId, packageName) returns
+        // null, the UPTC was never inserted.
+        ArrayMap<String, T> data = mData.get(userId, packageName);
+        return data != null && data.containsKey(tag);
+    }
+
+    /** Removes all the data for the user, if there was any. */
+    public void delete(int userId) {
+        mData.delete(userId);
+    }
+
+    /** Removes the data for the user, package, and tag, if there was any. */
+    public void delete(int userId, @NonNull String packageName, @Nullable String tag) {
+        final ArrayMap<String, T> data = mData.get(userId, packageName);
+        if (data != null) {
+            data.remove(tag);
+            if (data.size() == 0) {
+                mData.delete(userId, packageName);
+            }
+        }
+    }
+
+    /** Removes the data for the user and package, if there was any. */
+    public ArrayMap<String, T> delete(int userId, @NonNull String packageName) {
+        return mData.delete(userId, packageName);
+    }
+
+    /**
+     * Returns the set of tag -> object mappings for the given userId and packageName
+     * combination.
+     */
+    @Nullable
+    public ArrayMap<String, T> get(int userId, @NonNull String packageName) {
+        return mData.get(userId, packageName);
+    }
+
+    /** Returns the saved object for the given UPTC. */
+    @Nullable
+    public T get(int userId, @NonNull String packageName, @Nullable String tag) {
+        final ArrayMap<String, T> data = mData.get(userId, packageName);
+        return data != null ? data.get(tag) : null;
+    }
+
+    /**
+     * Returns the index for which {@link #getUserIdAtIndex(int)} would return the specified userId,
+     * or a negative number if the specified userId is not mapped.
+     */
+    public int indexOfUserId(int userId) {
+        return mData.indexOfKey(userId);
+    }
+
+    /**
+     * Returns the index for which {@link #getPackageNameAtIndex(int, int)} would return the
+     * specified userId, or a negative number if the specified userId and packageName are not mapped
+     * together.
+     */
+    public int indexOfUserIdAndPackage(int userId, @NonNull String packageName) {
+        return mData.indexOfKey(userId, packageName);
+    }
+
+    /** Returns the userId at the given index. */
+    public int getUserIdAtIndex(int index) {
+        return mData.keyAt(index);
+    }
+
+    /** Returns the package name at the given index. */
+    @NonNull
+    public String getPackageNameAtIndex(int userIndex, int packageIndex) {
+        return mData.keyAt(userIndex, packageIndex);
+    }
+
+    /** Returns the tag at the given index. */
+    @NonNull
+    public String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
+        // This structure never inserts a null ArrayMap, so if the indices are valid, valueAt()
+        // won't return null.
+        return mData.valueAt(userIndex, packageIndex).keyAt(tagIndex);
+    }
+
+    /** Returns the size of the outer (userId) array. */
+    public int userCount() {
+        return mData.numMaps();
+    }
+
+    /** Returns the number of packages saved for a given userId. */
+    public int packageCountForUser(int userId) {
+        return mData.numElementsForKey(userId);
+    }
+
+    /** Returns the number of tags saved for a given userId-packageName combination. */
+    public int tagCountForUserAndPackage(int userId, @NonNull String packageName) {
+        final ArrayMap data = mData.get(userId, packageName);
+        return data != null ? data.size() : 0;
+    }
+
+    /** Returns the value T at the given user, package, and tag indices. */
+    @Nullable
+    public T valueAt(int userIndex, int packageIndex, int tagIndex) {
+        final ArrayMap<String, T> data = mData.valueAt(userIndex, packageIndex);
+        return data != null ? data.valueAt(tagIndex) : null;
+    }
+
+    public void forEach(Consumer<T> consumer) {
+        mData.forEach((tagMap) -> {
+            for (int i = tagMap.size() - 1; i >= 0; --i) {
+                consumer.accept(tagMap.valueAt(i));
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6de9dbd..6bfa1ae 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1811,7 +1811,7 @@
         } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
             return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else if (taskSwitch && allowTaskSnapshot) {
-            return snapshot == null ? STARTING_WINDOW_TYPE_NONE
+            return snapshot == null ? STARTING_WINDOW_TYPE_SPLASH_SCREEN
                     : snapshotOrientationSameAsTask(snapshot) || fromRecents
                             ? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
         } else {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 677d2a1..6f124ac 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -3594,7 +3594,10 @@
             pw.println(prefix + "* " + task);
             task.dump(pw, prefix + "  ");
             final ArrayList<ActivityRecord> activities = new ArrayList<>();
-            forAllActivities((Consumer<ActivityRecord>) activities::add);
+            // Add activities by traversing the hierarchy from bottom to top, since activities
+            // are dumped in reverse order in {@link ActivityStackSupervisor#dumpHistoryList()}.
+            forAllActivities((Consumer<ActivityRecord>) activities::add,
+                    false /* traverseTopToBottom */);
             dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient,
                     dumpPackage, false, null, task);
         });
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 43dce73..26d9dbc 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2527,8 +2527,8 @@
 
     void scheduleUpdatePictureInPictureModeIfNeeded(Task task, ActivityStack prevStack) {
         final ActivityStack stack = task.getStack();
-        if (prevStack == null || prevStack == stack
-                || (!prevStack.inPinnedWindowingMode() && !stack.inPinnedWindowingMode())) {
+        if ((prevStack == null || (prevStack != stack
+                && !prevStack.inPinnedWindowingMode() && !stack.inPinnedWindowingMode()))) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 23083c9..674955e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -98,6 +98,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.AuxiliaryResolveInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
@@ -1309,9 +1310,11 @@
             String resolvedType, int userId) {
         if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) {
             // request phase two resolution
-            mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo(
+            PackageManagerInternal packageManager = mService.getPackageManagerInternalLocked();
+            boolean isRequesterInstantApp = packageManager.isInstantApp(callingPackage, userId);
+            packageManager.requestInstantAppResolutionPhaseTwo(
                     auxiliaryResponse, originalIntent, resolvedType, callingPackage,
-                    verificationBundle, userId);
+                    isRequesterInstantApp, verificationBundle, userId);
         }
         return InstantAppResolver.buildEphemeralInstallerIntent(
                 originalIntent,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e3ea2c5..4667eab 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4955,7 +4955,11 @@
         // Re-parent IME's SurfaceControl when MagnificationSpec changed.
         updateImeParent();
 
-        applyMagnificationSpec(getPendingTransaction(), spec);
+        if (spec.scale != 1.0) {
+            applyMagnificationSpec(getPendingTransaction(), spec);
+        } else {
+            clearMagnificationSpec(getPendingTransaction());
+        }
         getPendingTransaction().apply();
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d73cb50f..06cea37 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -244,6 +244,8 @@
     protected final Rect mTmpRect = new Rect();
     final Rect mTmpPrevBounds = new Rect();
 
+    private MagnificationSpec mLastMagnificationSpec;
+
     WindowContainer(WindowManagerService wms) {
         mWmService = wms;
         mPendingTransaction = wms.mTransactionFactory.get();
@@ -1726,6 +1728,7 @@
         if (shouldMagnify()) {
             t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
                     .setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+            mLastMagnificationSpec = spec;
         } else {
             for (int i = 0; i < mChildren.size(); i++) {
                 mChildren.get(i).applyMagnificationSpec(t, spec);
@@ -1733,6 +1736,17 @@
         }
     }
 
+    void clearMagnificationSpec(Transaction t) {
+        if (mLastMagnificationSpec != null) {
+            t.setMatrix(mSurfaceControl, 1, 0, 0, 1)
+                .setPosition(mSurfaceControl, 0, 0);
+        }
+        mLastMagnificationSpec = null;
+        for (int i = 0; i < mChildren.size(); i++) {
+            mChildren.get(i).clearMagnificationSpec(t);
+        }
+    }
+
     void prepareSurfaces() {
         // If a leash has been set when the transaction was committed, then the leash reparent has
         // been committed.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index eab8d05..6918c96 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2149,13 +2149,27 @@
             return false;
         }
 
-        final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
-        final int type = mAttrs.type;
+        if (PixelFormat.formatHasAlpha(mAttrs.format)) {
+            // Support legacy use cases where transparent windows can still be ime target with
+            // FLAG_NOT_FOCUSABLE and ALT_FOCUSABLE_IM set.
+            // Certain apps listen for IME insets using transparent windows and ADJUST_NOTHING to
+            // manually synchronize app content to IME animation b/144619551.
+            // TODO(b/145812508): remove this once new focus management is complete b/141738570
+            final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
+            final int type = mAttrs.type;
 
-        // Can only be an IME target if both FLAG_NOT_FOCUSABLE and FLAG_ALT_FOCUSABLE_IM are set or
-        // both are cleared...and not a starting window.
-        if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
-                && type != TYPE_APPLICATION_STARTING) {
+            // Can only be an IME target if both FLAG_NOT_FOCUSABLE and FLAG_ALT_FOCUSABLE_IM are
+            // set or both are cleared...and not a starting window.
+            if (fl != 0 && fl != (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)
+                    && type != TYPE_APPLICATION_STARTING) {
+                return false;
+            }
+        } else if (!WindowManager.LayoutParams.mayUseInputMethod(mAttrs.flags)
+                || mAttrs.type == TYPE_APPLICATION_STARTING) {
+            // Can be an IME target only if:
+            // 1. FLAG_NOT_FOCUSABLE is not set
+            // 2. FLAG_ALT_FOCUSABLE_IM is not set
+            // 3. not a starting window.
             return false;
         }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index fd8094c..a34b7fd 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -35,6 +35,7 @@
         "com_android_server_power_PowerManagerService.cpp",
         "com_android_server_security_VerityUtils.cpp",
         "com_android_server_SerialService.cpp",
+        "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
         "com_android_server_storage_AppFuseBridge.cpp",
         "com_android_server_SystemServer.cpp",
         "com_android_server_TestNetworkService.cpp",
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index dcff5a1..6811e6d 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -410,6 +410,21 @@
     return 0;
 }
 
+static void vibratorAlwaysOnEnable(JNIEnv* env, jclass, jlong id, jlong effect, jlong strength) {
+    auto status = halCall(&aidl::IVibrator::alwaysOnEnable, id,
+            static_cast<aidl::Effect>(effect), static_cast<aidl::EffectStrength>(strength));
+    if (!status.isOk()) {
+        ALOGE("vibratortAlwaysOnEnable command failed (%s).", status.toString8().string());
+    }
+}
+
+static void vibratorAlwaysOnDisable(JNIEnv* env, jclass, jlong id) {
+    auto status = halCall(&aidl::IVibrator::alwaysOnDisable, id);
+    if (!status.isOk()) {
+        ALOGE("vibratorAlwaysOnDisable command failed (%s).", status.toString8().string());
+    }
+}
+
 static const JNINativeMethod method_table[] = {
     { "vibratorExists", "()Z", (void*)vibratorExists },
     { "vibratorInit", "()V", (void*)vibratorInit },
@@ -422,6 +437,8 @@
     { "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl},
     { "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl},
     { "vibratorGetCapabilities", "()J", (void*)vibratorGetCapabilities},
+    { "vibratorAlwaysOnEnable", "(JJJ)V", (void*)vibratorAlwaysOnEnable},
+    { "vibratorAlwaysOnDisable", "(J)V", (void*)vibratorAlwaysOnDisable},
 };
 
 int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 906b568..be11b86 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -24,6 +24,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <linux/fsverity.h>
 #include <string.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
@@ -33,40 +34,6 @@
 
 #include <android-base/unique_fd.h>
 
-// TODO(112037636): Always include once fsverity.h is upstreamed.
-#if __has_include(<linux/fsverity.h>)
-#include <linux/fsverity.h>
-#else
-
-#include <linux/limits.h>
-#include <linux/ioctl.h>
-#include <linux/types.h>
-
-#define FS_VERITY_HASH_ALG_SHA256	1
-
-struct fsverity_enable_arg {
-	__u32 version;
-	__u32 hash_algorithm;
-	__u32 block_size;
-	__u32 salt_size;
-	__u64 salt_ptr;
-	__u32 sig_size;
-	__u32 __reserved1;
-	__u64 sig_ptr;
-	__u64 __reserved2[11];
-};
-
-struct fsverity_digest {
-    __u16 digest_algorithm;
-    __u16 digest_size; /* input/output */
-    __u8 digest[];
-};
-
-#define FS_IOC_ENABLE_VERITY	_IOW('f', 133, struct fsverity_enable_arg)
-#define FS_IOC_MEASURE_VERITY	_IOWR('f', 134, struct fsverity_digest)
-
-#endif
-
 const int kSha256Bytes = 32;
 
 namespace android {
diff --git a/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
new file mode 100644
index 0000000..774534f
--- /dev/null
+++ b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sstream>
+
+#include "core_jni_helpers.h"
+#include <media/AudioSystem.h>
+
+namespace android {
+
+namespace {
+
+#define PACKAGE "com/android/server/soundtrigger_middleware"
+#define CLASSNAME PACKAGE "/AudioSessionProviderImpl"
+#define SESSION_CLASSNAME PACKAGE "/SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession"
+
+jobject acquireAudioSession(
+        JNIEnv* env,
+        jobject clazz) {
+
+    audio_session_t session;
+    audio_io_handle_t ioHandle;
+    audio_devices_t device;
+
+    status_t status = AudioSystem::acquireSoundTriggerSession(&session,
+                                                              &ioHandle,
+                                                              &device);
+    if (status != 0) {
+        std::ostringstream message;
+        message
+                << "AudioSystem::acquireSoundTriggerSession returned an error code: "
+                << status;
+        env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+                      message.str().c_str());
+        return nullptr;
+    }
+
+    jclass cls = FindClassOrDie(env, SESSION_CLASSNAME);
+    jmethodID ctor = GetMethodIDOrDie(env, cls, "<init>", "(III)V");
+    return env->NewObject(cls,
+                          ctor,
+                          static_cast<int>(session),
+                          static_cast<int>(ioHandle),
+                          static_cast<int>(device));
+}
+
+void releaseAudioSession(JNIEnv* env, jobject clazz, jint handle) {
+    status_t status =
+            AudioSystem::releaseSoundTriggerSession(static_cast<audio_session_t>(handle));
+
+    if (status != 0) {
+        std::ostringstream message;
+        message
+                << "AudioSystem::releaseAudioSystemSession returned an error code: "
+                << status;
+        env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+                      message.str().c_str());
+    }
+}
+
+const JNINativeMethod g_methods[] = {
+        {"acquireSession", "()L" SESSION_CLASSNAME ";",
+         reinterpret_cast<void*>(acquireAudioSession)},
+        {"releaseSession", "(I)V",
+         reinterpret_cast<void*>(releaseAudioSession)},
+};
+
+}  // namespace
+
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+        JNIEnv* env) {
+    return RegisterMethodsOrDie(env,
+                                CLASSNAME,
+                                g_methods,
+                                NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 692c9d2..165edf1 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,8 @@
 int register_android_server_security_VerityUtils(JNIEnv* env);
 int register_android_server_am_AppCompactor(JNIEnv* env);
 int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+        JNIEnv* env);
 };
 
 using namespace android;
@@ -105,5 +107,7 @@
     register_android_server_security_VerityUtils(env);
     register_android_server_am_AppCompactor(env);
     register_android_server_am_LowMemDetector(env);
+    register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+            env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 078bab4..d91ec42 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2447,7 +2447,7 @@
                 Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId) != 0) {
             profileOwner.ensureUserRestrictions().putBoolean(
                     UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
-            saveUserRestrictionsLocked(userId);
+            saveUserRestrictionsLocked(userId, /* parent = */ false);
             mInjector.settingsSecurePutIntForUser(
                     Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, 0, userId);
         }
@@ -2478,7 +2478,7 @@
             }
             admin.defaultEnabledRestrictionsAlreadySet.addAll(restrictionsToSet);
             Slog.i(LOG_TAG, "Enabled the following restrictions by default: " + restrictionsToSet);
-            saveUserRestrictionsLocked(userId);
+            saveUserRestrictionsLocked(userId, /* parent = */ false);
         }
     }
 
@@ -8039,7 +8039,7 @@
                 activeAdmin.defaultEnabledRestrictionsAlreadySet.addAll(restrictions);
                 Slog.i(LOG_TAG, "Enabled the following restrictions by default: " + restrictions);
 
-                saveUserRestrictionsLocked(userId);
+                saveUserRestrictionsLocked(userId, /* parent = */ false);
             }
 
             long ident = mInjector.binderClearCallingIdentity();
@@ -10310,24 +10310,33 @@
     }
 
     @Override
-    public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
+    public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner,
+            boolean parent) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         if (!UserRestrictionsUtils.isValidRestriction(key)) {
             return;
         }
 
-        final int userHandle = mInjector.userHandleGetCallingUserId();
+        int userHandle = mInjector.userHandleGetCallingUserId();
         synchronized (getLockObject()) {
             final ActiveAdmin activeAdmin =
                     getActiveAdminForCallerLocked(who,
-                            DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+                            DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
             final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+
             if (isDeviceOwner) {
                 if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
                     throw new SecurityException("Device owner cannot set user restriction " + key);
                 }
-            } else { // profile owner
-                if (!UserRestrictionsUtils.canProfileOwnerChange(key, userHandle)) {
+                if (parent) {
+                    throw new IllegalArgumentException(
+                            "Cannot use the parent instance in Device Owner mode");
+                }
+            } else {
+                if (!(UserRestrictionsUtils.canProfileOwnerChange(key, userHandle) || (
+                        isProfileOwnerOfOrganizationOwnedDevice(activeAdmin) && parent
+                        && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
+                                key)))) {
                     throw new SecurityException("Profile owner cannot set user restriction " + key);
                 }
             }
@@ -10339,7 +10348,7 @@
             } else {
                 restrictions.remove(key);
             }
-            saveUserRestrictionsLocked(userHandle);
+            saveUserRestrictionsLocked(userHandle, parent);
         }
         final int eventId = enabledFromThisOwner
                 ? DevicePolicyEnums.ADD_USER_RESTRICTION
@@ -10357,9 +10366,9 @@
         }
     }
 
-    private void saveUserRestrictionsLocked(int userId) {
+    private void saveUserRestrictionsLocked(int userId, boolean parent) {
         saveSettingsLocked(userId);
-        pushUserRestrictions(userId);
+        pushUserRestrictions(parent ? getProfileParentId(userId) : userId);
         sendChangedNotification(userId);
     }
 
@@ -10368,6 +10377,7 @@
             final boolean isDeviceOwner = mOwners.isDeviceOwnerUserId(userId);
             Bundle userRestrictions = null;
             final int restrictionOwnerType;
+            final int originatingUserId;
 
             if (isDeviceOwner) {
                 final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
@@ -10377,6 +10387,7 @@
                 userRestrictions = deviceOwner.userRestrictions;
                 addOrRemoveDisableCameraRestriction(userRestrictions, deviceOwner);
                 restrictionOwnerType = UserManagerInternal.OWNER_TYPE_DEVICE_OWNER;
+                originatingUserId = deviceOwner.getUserHandle().getIdentifier();
             } else {
                 final ActiveAdmin profileOwnerOfOrganizationOwnedDevice =
                         getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
@@ -10391,21 +10402,25 @@
                     userRestrictions = parent.userRestrictions;
                     userRestrictions = addOrRemoveDisableCameraRestriction(userRestrictions,
                             parent);
+                    originatingUserId =
+                            profileOwnerOfOrganizationOwnedDevice.getUserHandle().getIdentifier();
                 } else {
                     final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
 
                     if (profileOwner != null) {
                         userRestrictions = profileOwner.userRestrictions;
                         restrictionOwnerType = UserManagerInternal.OWNER_TYPE_PROFILE_OWNER;
+                        originatingUserId = profileOwner.getUserHandle().getIdentifier();
                     } else {
                         restrictionOwnerType = UserManagerInternal.OWNER_TYPE_NO_OWNER;
+                        originatingUserId = userId;
                     }
                     userRestrictions = addOrRemoveDisableCameraRestriction(
                             userRestrictions, userId);
                 }
             }
-            mUserManagerInternal.setDevicePolicyUserRestrictions(userId, userRestrictions,
-                    restrictionOwnerType);
+            mUserManagerInternal.setDevicePolicyUserRestrictions(originatingUserId,
+                    userRestrictions, restrictionOwnerType);
         }
     }
 
@@ -10435,14 +10450,18 @@
     }
 
     @Override
-    public Bundle getUserRestrictions(ComponentName who) {
+    public Bundle getUserRestrictions(ComponentName who, boolean parent) {
         if (!mHasFeature) {
             return null;
         }
         Preconditions.checkNotNull(who, "ComponentName is null");
+
         synchronized (getLockObject()) {
             final ActiveAdmin activeAdmin = getActiveAdminForCallerLocked(who,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+            if (parent) {
+                enforceProfileOwnerOfOrganizationOwnedDevice(activeAdmin);
+            }
             return activeAdmin.userRestrictions;
         }
     }
@@ -11241,7 +11260,7 @@
                 } else {
                     try {
                         setUserRestriction(who, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
-                                (Integer.parseInt(value) == 0) ? true : false);
+                                (Integer.parseInt(value) == 0) ? true : false, /* parent */ false);
                         DevicePolicyEventLogger
                                 .createEvent(DevicePolicyEnums.SET_SECURE_SETTING)
                                 .setAdmin(who)
@@ -11286,7 +11305,7 @@
         Preconditions.checkNotNull(who, "ComponentName is null");
         synchronized (getLockObject()) {
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on);
+            setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on, /* parent */ false);
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.SET_MASTER_VOLUME_MUTED)
                     .setAdmin(who)
@@ -13773,8 +13792,9 @@
         } else {
             deviceOwner.lastNetworkLoggingNotificationTimeMs = now;
         }
+        final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
         final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
-        intent.setPackage("com.android.systemui");
+        intent.setPackage(pm.getSystemUiServiceComponent().getPackageName());
         final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0,
                 UserHandle.CURRENT);
         Notification notification =
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 401a094..a1e0237 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -30,12 +30,12 @@
 import android.app.AppCompatCallbacks;
 import android.app.INotificationManager;
 import android.app.usage.UsageStatsManagerInternal;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
@@ -105,6 +105,7 @@
 import com.android.server.gpu.GpuService;
 import com.android.server.hdmi.HdmiControlService;
 import com.android.server.incident.IncidentCompanionService;
+import com.android.server.incremental.IncrementalManagerService;
 import com.android.server.input.InputManagerService;
 import com.android.server.inputmethod.InputMethodManagerService;
 import com.android.server.inputmethod.InputMethodSystemProperty;
@@ -148,6 +149,7 @@
 import com.android.server.security.KeyChainSystemService;
 import com.android.server.signedconfig.SignedConfigService;
 import com.android.server.soundtrigger.SoundTriggerService;
+import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
@@ -323,6 +325,7 @@
     private ContentResolver mContentResolver;
     private EntropyMixer mEntropyMixer;
     private DataLoaderManagerService mDataLoaderManagerService;
+    private IncrementalManagerService mIncrementalManagerService;
 
     private boolean mOnlyCore;
     private boolean mFirstBoot;
@@ -705,6 +708,11 @@
                 DataLoaderManagerService.class);
         t.traceEnd();
 
+        // Incremental service needs to be started before package manager
+        t.traceBegin("StartIncrementalManagerService");
+        mIncrementalManagerService = IncrementalManagerService.start(mSystemContext);
+        t.traceEnd();
+
         // Power manager needs to be started early because other services need it.
         // Native daemons may be watching for it to be registered so it must be ready
         // to handle incoming binder calls immediately (including being able to verify
@@ -1555,6 +1563,10 @@
             }
             t.traceEnd();
 
+            t.traceBegin("StartSoundTriggerMiddlewareService");
+            mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+            t.traceEnd();
+
             if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BROADCAST_RADIO)) {
                 t.traceBegin("StartBroadcastRadioService");
                 mSystemServiceManager.startService(BroadcastRadioService.class);
@@ -2061,6 +2073,12 @@
         mPackageManagerService.systemReady();
         t.traceEnd();
 
+        if (mIncrementalManagerService != null) {
+            t.traceBegin("MakeIncrementalManagerServiceReady");
+            mIncrementalManagerService.systemReady();
+            t.traceEnd();
+        }
+
         t.traceBegin("MakeDisplayManagerServiceReady");
         try {
             // TODO: use boot phase and communicate these flags some other way
@@ -2395,9 +2413,9 @@
     }
 
     private static void startSystemUi(Context context, WindowManagerService windowManager) {
+        PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
         Intent intent = new Intent();
-        intent.setComponent(new ComponentName("com.android.systemui",
-                "com.android.systemui.SystemUIService"));
+        intent.setComponent(pm.getSystemUiServiceComponent());
         intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
         //Slog.d(TAG, "Starting service: " + intent);
         context.startServiceAsUser(intent, UserHandle.SYSTEM);
diff --git a/services/net/java/android/net/TcpKeepalivePacketData.java b/services/net/java/android/net/TcpKeepalivePacketData.java
index 7f2f499..aad75ae 100644
--- a/services/net/java/android/net/TcpKeepalivePacketData.java
+++ b/services/net/java/android/net/TcpKeepalivePacketData.java
@@ -19,7 +19,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.SocketKeepalive.InvalidPacketException;
 import android.net.util.IpUtils;
 import android.os.Parcel;
 import android.os.Parcelable;
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index cf7919b..3910993 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -40,7 +40,6 @@
         "platformprotosnano",
         "hamcrest-library",
         "servicestests-utils",
-        "xml-writer-device-lib",
         "service-appsearch",
         "service-jobscheduler",
     ],
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
similarity index 83%
rename from services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
index b707912..538e2d5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/AccessibilityGestureDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/GestureManifoldTest.java
@@ -24,22 +24,21 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
-import android.util.DisplayMetrics;
-import android.view.GestureDetector;
 import android.view.MotionEvent;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.Before;
 import org.junit.Test;
 
 import java.util.ArrayList;
 
 /**
- * Tests for AccessibilityGestureDetector
+ * Tests for GestureManifold
  */
-public class AccessibilityGestureDetectorTest {
+public class GestureManifoldTest {
 
     // Constants for testRecognizeGesturePath()
     private static final PointF PATH_START = new PointF(300f, 300f);
@@ -47,24 +46,21 @@
     private static final long PATH_STEP_MILLISEC = 100;
 
     // Data used by all tests
-    private AccessibilityGestureDetector mDetector;
-    private AccessibilityGestureDetector.Listener mResultListener;
+    private GestureManifold mManifold;
+    private TouchState mState;
+    private GestureManifold.Listener mResultListener;
 
     @Before
     public void setUp() {
-        // Construct a mock Context.
-        DisplayMetrics displayMetricsMock = mock(DisplayMetrics.class);
-        displayMetricsMock.xdpi = 500;
-        displayMetricsMock.ydpi = 500;
-        Resources mockResources = mock(Resources.class);
-        when(mockResources.getDisplayMetrics()).thenReturn(displayMetricsMock);
-        Context contextMock = mock(Context.class);
-        when(contextMock.getResources()).thenReturn(mockResources);
+        Context context = InstrumentationRegistry.getContext();
+        // Construct a testable GestureManifold.
+        mResultListener = mock(GestureManifold.Listener.class);
+        mState = new TouchState();
+        mManifold = new GestureManifold(context, mResultListener, mState);
+        // Play the role of touch explorer in updating the shared state.
+        when(mResultListener.onGestureStarted()).thenReturn(onGestureStarted());
 
-        // Construct a testable AccessibilityGestureDetector.
-        mResultListener = mock(AccessibilityGestureDetector.Listener.class);
-        GestureDetector doubleTapDetectorMock = mock(GestureDetector.class);
-        mDetector = new AccessibilityGestureDetector(contextMock, mResultListener, doubleTapDetectorMock);
+
     }
 
 
@@ -141,8 +137,8 @@
         // For each path step from start (non-inclusive) to end ... add a motion point.
         for (int step = 1; step < numSteps; ++step) {
             path.add(new PointF(
-                (start.x + (stepX * (float) step)),
-                (start.y + (stepY * (float) step))));
+                    (start.x + (stepX * (float) step)),
+                    (start.y + (stepY * (float) step))));
         }
     }
 
@@ -170,12 +166,22 @@
                     point.x, point.y, 0);
 
             // Send event.
-            mDetector.onMotionEvent(event, event, policyFlags);
+            mState.onReceivedMotionEvent(event);
+            mManifold.onMotionEvent(event, event, policyFlags);
             eventTimeMs += PATH_STEP_MILLISEC;
+            if (mState.isClear()) {
+                mState.startTouchInteracting();
+            }
         }
 
+        mState.clear();
         // Check that correct gesture was recognized.
         verify(mResultListener).onGestureCompleted(
                 argThat(gestureEvent -> gestureEvent.getGestureId() == gestureId));
     }
+
+    private boolean onGestureStarted() {
+        mState.startGestureDetecting();
+        return false;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 4b1ec6f..a4ceadb 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -74,7 +74,7 @@
     private TouchExplorer mTouchExplorer;
     private long mLastDownTime = Integer.MIN_VALUE;
 
-    // mock package-private AccessibilityGestureDetector class
+    // mock package-private GestureManifold class
     @Rule
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
@@ -108,7 +108,7 @@
     public void setUp() {
         Context context = InstrumentationRegistry.getContext();
         AccessibilityManagerService ams = new AccessibilityManagerService(context);
-        AccessibilityGestureDetector detector = mock(AccessibilityGestureDetector.class);
+        GestureManifold detector = mock(GestureManifold.class);
         mCaptor = new EventCaptor();
         mTouchExplorer = new TouchExplorer(context, ams, detector);
         mTouchExplorer.setNext(mCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 49412bc0c..2ce17a1 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -349,10 +349,21 @@
         verifyUidRangesNoOverlap(range, range2);
         verifyIsolatedUidAllocator(range2);
 
-        // Free both, then try to allocate the maximum number of UID ranges
+        // Free both
         allocator.freeUidRangeLocked(appInfo);
         allocator.freeUidRangeLocked(appInfo2);
 
+        // Verify for a secondary user
+        ApplicationInfo appInfo3 = new ApplicationInfo();
+        appInfo3.processName = "com.android.test.app";
+        appInfo3.uid = 1010000;
+        final IsolatedUidRange range3 = allocator.getOrCreateIsolatedUidRangeLocked(
+                appInfo3.processName, appInfo3.uid);
+        validateAppZygoteIsolatedUidRange(range3);
+        verifyIsolatedUidAllocator(range3);
+
+        allocator.freeUidRangeLocked(appInfo3);
+        // Try to allocate the maximum number of UID ranges
         int maxNumUidRanges = (Process.LAST_APP_ZYGOTE_ISOLATED_UID
                 - Process.FIRST_APP_ZYGOTE_ISOLATED_UID + 1) / Process.NUM_UIDS_PER_APP_ZYGOTE;
         for (int i = 0; i < maxNumUidRanges; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index a47a567..e90cb46 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -16,7 +16,11 @@
 
 package com.android.server.attention;
 
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
 import static com.android.server.attention.AttentionManagerService.ATTENTION_CACHE_BUFFER_SIZE;
+import static com.android.server.attention.AttentionManagerService.DEFAULT_STALE_AFTER_MILLIS;
+import static com.android.server.attention.AttentionManagerService.KEY_STALE_AFTER_MILLIS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -35,6 +39,7 @@
 import android.os.IPowerManager;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.provider.DeviceConfig;
 import android.service.attention.IAttentionCallback;
 import android.service.attention.IAttentionService;
 
@@ -180,6 +185,45 @@
         assertThat(buffer.get(0)).isEqualTo(cache);
     }
 
+    @Test
+    public void testGetStaleAfterMillis_handlesGoodFlagValue() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "123", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(123);
+    }
+
+    @Test
+    public void testGetStaleAfterMillis_handlesBadFlagValue_1() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "-123", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+                DEFAULT_STALE_AFTER_MILLIS);
+    }
+
+    @Test
+    public void testGetStaleAfterMillis_handlesBadFlagValue_2() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "15000", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+                DEFAULT_STALE_AFTER_MILLIS);
+    }
+
+    @Test
+    public void testGetStaleAfterMillis_handlesBadFlagValue_3() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "abracadabra", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+                DEFAULT_STALE_AFTER_MILLIS);
+    }
+
+    @Test
+    public void testGetStaleAfterMillis_handlesBadFlagValue_4() {
+        DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+                KEY_STALE_AFTER_MILLIS, "15_000L", false);
+        assertThat(mSpyAttentionManager.getStaleAfterMillis()).isEqualTo(
+                DEFAULT_STALE_AFTER_MILLIS);
+    }
+
     private class MockIAttentionService implements IAttentionService {
         public void checkAttention(IAttentionCallback callback) throws RemoteException {
             callback.onSuccess(0, 0);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 106a723..d38c80c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -20,6 +20,7 @@
 
 import static junit.framework.Assert.assertEquals;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -65,8 +66,16 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        // Dummy test config
+        final String[] config = {
+                "0:2:15", // ID0:Fingerprint:Strong
+                "1:4:15", // ID1:Iris:Strong
+                "2:8:15", // ID2:Face:Strong
+        };
+
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mInjector.getBiometricService()).thenReturn(mBiometricService);
+        when(mInjector.getConfiguration(any())).thenReturn(config);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
                 .thenReturn(true);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(true);
@@ -76,8 +85,7 @@
 
     // TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
     @Test
-    public void testAuthenticate_callsBiometricServiceAuthenticate() throws
-            Exception {
+    public void testAuthenticate_callsBiometricServiceAuthenticate() throws Exception {
         mAuthService = new AuthService(mContext, mInjector);
         mAuthService.onStart();
 
@@ -104,22 +112,25 @@
     }
 
     @Test
-    public void testCanAuthenticate_callsBiometricServiceCanAuthenticate() throws
-            Exception {
+    public void testCanAuthenticate_callsBiometricServiceCanAuthenticate() throws Exception {
         mAuthService = new AuthService(mContext, mInjector);
         mAuthService.onStart();
 
         final int userId = 0;
         final int expectedResult = BIOMETRIC_SUCCESS;
-        when(mBiometricService.canAuthenticate(anyString(), anyInt())).thenReturn(expectedResult);
+        final int authenticators = 0;
+        when(mBiometricService.canAuthenticate(anyString(), anyInt(), anyInt()))
+                .thenReturn(expectedResult);
 
-        final int result = mAuthService.mImpl.canAuthenticate(TEST_OP_PACKAGE_NAME, userId);
+        final int result = mAuthService.mImpl
+                .canAuthenticate(TEST_OP_PACKAGE_NAME, userId, authenticators);
 
         assertEquals(expectedResult, result);
         waitForIdle();
         verify(mBiometricService).canAuthenticate(
                 eq(TEST_OP_PACKAGE_NAME),
-                eq(userId));
+                eq(userId),
+                eq(authenticators));
     }
 
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 4ced421..211fc4d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,12 +16,14 @@
 
 package com.android.server.biometrics;
 
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertNotNull;
 
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,12 +36,13 @@
 import static org.mockito.Mockito.when;
 
 import android.app.IActivityManager;
+import android.app.trust.ITrustManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
-import android.hardware.biometrics.Authenticator;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricService;
@@ -81,8 +84,6 @@
 
     private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
 
-    private static final int STRENGTH_STRONG = 1;
-
     private BiometricService mBiometricService;
 
     @Mock
@@ -101,6 +102,8 @@
     IBiometricAuthenticator mFingerprintAuthenticator;
     @Mock
     IBiometricAuthenticator mFaceAuthenticator;
+    @Mock
+    ITrustManager mTrustManager;
 
     @Before
     public void setUp() {
@@ -111,10 +114,13 @@
 
         when(mInjector.getActivityManagerService()).thenReturn(mock(IActivityManager.class));
         when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
-        when(mInjector.getSettingObserver(any(), any(), any())).thenReturn(
-                mock(BiometricService.SettingObserver.class));
+        when(mInjector.getSettingObserver(any(), any(), any()))
+                .thenReturn(mock(BiometricService.SettingObserver.class));
         when(mInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
         when(mInjector.isDebugEnabled(any(), anyInt())).thenReturn(false);
+        when(mInjector.getBiometricStrengthController(any()))
+                .thenReturn(mock(BiometricStrengthController.class));
+        when(mInjector.getTrustManager()).thenReturn(mTrustManager);
 
         when(mResources.getString(R.string.biometric_error_hw_unavailable))
                 .thenReturn(ERROR_HW_UNAVAILABLE);
@@ -122,6 +128,56 @@
                 .thenReturn(ERROR_NOT_RECOGNIZED);
         when(mResources.getString(R.string.biometric_error_user_canceled))
                 .thenReturn(ERROR_USER_CANCELED);
+
+        final String[] config = {
+                "0:2:15",  // ID0:Fingerprint:Strong
+                "1:8:15",  // ID1:Face:Strong
+                "2:4:255", // ID2:Iris:Weak
+        };
+
+        when(mInjector.getConfiguration(any())).thenReturn(config);
+    }
+
+    @Test
+    public void testAuthenticate_credentialAllowedButNotSetup_returnsNoDeviceCredential()
+            throws Exception {
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
+
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                Authenticators.DEVICE_CREDENTIAL);
+        waitForIdle();
+        verify(mReceiver1).onError(
+                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL),
+                eq(0 /* vendorCode */));
+    }
+
+    @Test
+    public void testAuthenticate_credentialAllowedAndSetup_callsSystemUI() throws Exception {
+        // When no biometrics are enrolled, but credentials are set up, status bar should be
+        // invoked right away with showAuthenticationDialog
+
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                Authenticators.DEVICE_CREDENTIAL);
+        waitForIdle();
+
+        assertNull(mBiometricService.mPendingAuthSession);
+        // StatusBar showBiometricDialog invoked
+        verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+                eq(mBiometricService.mCurrentAuthSession.mBundle),
+                any(IBiometricServiceReceiverInternal.class),
+                eq(0),
+                anyBoolean() /* requireConfirmation */,
+                anyInt() /* userId */,
+                eq(TEST_PACKAGE_NAME));
     }
 
     @Test
@@ -131,7 +187,7 @@
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                false /* allowDeviceCredential */);
+                null /* authenticators */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
@@ -145,12 +201,12 @@
 
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
-        mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG,
-                BiometricAuthenticator.TYPE_FINGERPRINT, mFingerprintAuthenticator);
-
+        mBiometricService.mImpl.registerAuthenticator(0 /* id */,
+                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+                mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                false /* allowDeviceCredential */);
+                null /* authenticators */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_FINGERPRINT),
@@ -159,6 +215,54 @@
     }
 
     @Test
+    public void testAuthenticate_notStrongEnough_returnsHardwareNotPresent() throws Exception {
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                Authenticators.BIOMETRIC_STRONG);
+        waitForIdle();
+        verify(mReceiver1).onError(
+                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+                eq(0 /* vendorCode */));
+    }
+
+    @Test
+    public void testAuthenticate_picksStrongIfAvailable() throws Exception {
+        // If both strong and weak are available, and the caller requires STRONG, authentication
+        // is able to proceed.
+
+        final int[] modalities = new int[] {
+                BiometricAuthenticator.TYPE_FINGERPRINT,
+                BiometricAuthenticator.TYPE_FACE,
+        };
+
+        final int[] strengths = new int[] {
+                Authenticators.BIOMETRIC_WEAK,
+                Authenticators.BIOMETRIC_STRONG,
+        };
+
+        setupAuthForMultiple(modalities, strengths);
+
+        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG);
+        waitForIdle();
+        verify(mReceiver1, never()).onError(
+                anyInt(),
+                anyInt(),
+                anyInt() /* vendorCode */);
+
+        // StatusBar showBiometricDialog invoked with face, which was set up to be STRONG
+        verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+                eq(mBiometricService.mCurrentAuthSession.mBundle),
+                any(IBiometricServiceReceiverInternal.class),
+                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(false) /* requireConfirmation */,
+                anyInt() /* userId */,
+                eq(TEST_PACKAGE_NAME));
+    }
+
+    @Test
     public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws
             Exception {
         when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
@@ -166,11 +270,12 @@
 
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
-        mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG,
-                BiometricAuthenticator.TYPE_FINGERPRINT, mFingerprintAuthenticator);
+        mBiometricService.mImpl.registerAuthenticator(0 /* id */,
+                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+                mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                false /* allowDeviceCredential */);
+                null /* authenticators */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
@@ -181,18 +286,12 @@
     @Test
     public void testAuthenticateFace_respectsUserSetting()
             throws Exception {
-        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
-        when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
-
-        mBiometricService = new BiometricService(mContext, mInjector);
-        mBiometricService.onStart();
-        mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG,
-                BiometricAuthenticator.TYPE_FACE, mFaceAuthenticator);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
 
         // Disabled in user settings receives onError
         when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                false /* allowDeviceCredential */);
+                null /* authenticators */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
@@ -205,7 +304,7 @@
         when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
                 .thenReturn(true);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                false /* allowDeviceCredential */);
+                null /* authenticators */);
         waitForIdle();
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
         verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
@@ -225,7 +324,7 @@
         when(mBiometricService.mSettingObserver.getFaceAlwaysRequireConfirmation(anyInt()))
                 .thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                false /* allowDeviceCredential */);
+                null /* authenticators */);
         waitForIdle();
         verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
                 eq(false) /* requireConfirmation */,
@@ -242,11 +341,11 @@
 
     @Test
     public void testAuthenticate_happyPathWithoutConfirmation() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
 
         // Start testing the happy path
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                false /* allowDeviceCredential */);
+                null /* authenticators */);
         waitForIdle();
 
         // Creates a pending auth session with the correct initial states
@@ -314,15 +413,16 @@
 
     @Test
     public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                true /* requireConfirmation */, true /* allowDeviceCredential */);
+                true /* requireConfirmation */,
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
         waitForIdle();
 
         assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
                 mBiometricService.mCurrentAuthSession.mState);
-        assertEquals(Authenticator.TYPE_CREDENTIAL,
+        assertEquals(Authenticators.DEVICE_CREDENTIAL,
                 mBiometricService.mCurrentAuthSession.mBundle
                         .getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -336,9 +436,9 @@
 
     @Test
     public void testAuthenticate_happyPathWithConfirmation() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                true /* requireConfirmation */, false /* allowDeviceCredential */);
+                true /* requireConfirmation */, null /* authenticators */);
 
         // Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not
         // sent to KeyStore yet
@@ -362,9 +462,9 @@
     @Test
     public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver.onAuthenticationFailed();
         waitForIdle();
@@ -381,9 +481,9 @@
     @Test
     public void testRejectFingerprint_whenAuthenticating_notifiesAndKeepsAuthenticating()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver.onAuthenticationFailed();
         waitForIdle();
@@ -398,53 +498,10 @@
     }
 
     @Test
-    public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws
-            Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
-        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
-
-        // Create a new pending auth session but don't start it yet. HAL contract is that previous
-        // one must get ERROR_CANCELED. Simulate that here by creating the pending auth session,
-        // sending ERROR_CANCELED to the current auth session, and then having the second one
-        // onReadyForAuthentication.
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */,
-                false /* allowDeviceCredential */);
-        waitForIdle();
-
-        assertEquals(mBiometricService.mCurrentAuthSession.mState,
-                BiometricService.STATE_AUTH_STARTED);
-        mBiometricService.mInternalReceiver.onError(
-                getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
-                BiometricAuthenticator.TYPE_FINGERPRINT,
-                BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
-        waitForIdle();
-
-        // Auth session doesn't become null until SystemUI responds that the animation is completed
-        assertNotNull(mBiometricService.mCurrentAuthSession);
-        // ERROR_CANCELED is not sent until SystemUI responded that animation is completed
-        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
-        verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
-
-        // SystemUI dialog closed
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
-
-        // After SystemUI notifies that the animation has completed
-        mBiometricService.mInternalReceiver
-                .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
-        waitForIdle();
-        verify(mReceiver1).onError(
-                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
-                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
-                eq(0 /* vendorCode */));
-        assertNull(mBiometricService.mCurrentAuthSession);
-    }
-
-    @Test
     public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -491,9 +548,9 @@
 
     @Test
     public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -524,9 +581,9 @@
         // For errors that show in SystemUI, BiometricService stays in STATE_ERROR_PENDING_SYSUI
         // until SystemUI notifies us that the dialog is dismissed at which point the current
         // session is done.
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -558,9 +615,10 @@
 
     @Test
     public void testErrorFromHal_whilePreparingAuthentication_credentialAllowed() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, true /* allowDeviceCredential */);
+                false /* requireConfirmation */,
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
         waitForIdle();
 
         mBiometricService.mInternalReceiver.onError(
@@ -576,7 +634,7 @@
         assertNotNull(mBiometricService.mCurrentAuthSession);
         assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
                 mBiometricService.mCurrentAuthSession.mState);
-        assertEquals(Authenticator.TYPE_CREDENTIAL,
+        assertEquals(Authenticators.DEVICE_CREDENTIAL,
                 mBiometricService.mCurrentAuthSession.mBundle.getInt(
                         BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -591,9 +649,9 @@
     @Test
     public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
         waitForIdle();
 
         mBiometricService.mInternalReceiver.onError(
@@ -609,58 +667,64 @@
     }
 
     @Test
-    public void testCombineAuthenticatorBundle_keyAllowDeviceCredentialAlwaysRemoved() {
-        Bundle bundle;
-        int authenticators;
+    public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() {
+        final boolean allowDeviceCredential = false;
+        final @Authenticators.Types int authenticators =
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+        final Bundle bundle = new Bundle();
 
-        // In:
-        // KEY_ALLOW_DEVICE_CREDENTIAL = true
-        // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
-        // Out:
-        // KEY_ALLOW_DEVICE_CREDENTIAL = null
-        // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
-        bundle = new Bundle();
-        bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
-        authenticators = Authenticator.TYPE_CREDENTIAL | Authenticator.TYPE_BIOMETRIC;
+        bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
         bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
         Utils.combineAuthenticatorBundles(bundle);
-        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
-        assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
 
-        // In:
-        // KEY_ALLOW_DEVICE_CREDENTIAL = true
-        // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC
-        // Out:
-        // KEY_ALLOW_DEVICE_CREDENTIAL = null
-        // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
-        bundle = new Bundle();
-        bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
-        authenticators = Authenticator.TYPE_BIOMETRIC;
+        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+        assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+    }
+
+    @Test
+    public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andKeyAuthenticators() {
+        final @Authenticators.Types int authenticators =
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+        final Bundle bundle = new Bundle();
+
         bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
         Utils.combineAuthenticatorBundles(bundle);
-        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
-        assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
 
-        // In:
-        // KEY_ALLOW_DEVICE_CREDENTIAL = null
-        // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
-        // Out:
-        // KEY_ALLOW_DEVICE_CREDENTIAL = null
-        // KEY_AUTHENTICATORS_ALLOWED = TYPE_BIOMETRIC | TYPE_CREDENTIAL
-        bundle = new Bundle();
-        authenticators = Authenticator.TYPE_BIOMETRIC | Authenticator.TYPE_CREDENTIAL;
-        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
-        Utils.combineAuthenticatorBundles(bundle);
         assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
-        assertEquals(authenticators, bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+        assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+    }
+
+    @Test
+    public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andNoKeyAuthenticators() {
+        final boolean allowDeviceCredential = true;
+        final Bundle bundle = new Bundle();
+
+        bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
+        Utils.combineAuthenticatorBundles(bundle);
+
+        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+        assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+    }
+
+    @Test
+    public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andNoKeyAuthenticators() {
+        final Bundle bundle = new Bundle();
+
+        Utils.combineAuthenticatorBundles(bundle);
+
+        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+        assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+                Authenticators.BIOMETRIC_WEAK);
     }
 
     @Test
     public void testErrorFromHal_whileShowingDeviceCredential_doesntNotifySystemUI()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, true /* allowDeviceCredential */);
+                false /* requireConfirmation */,
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
 
         mBiometricService.mInternalReceiver.onDeviceCredentialPressed();
         waitForIdle();
@@ -683,9 +747,10 @@
 
     @Test
     public void testLockout_whileAuthenticating_credentialAllowed() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, true /* allowDeviceCredential */);
+                false /* requireConfirmation */,
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
 
         assertEquals(BiometricService.STATE_AUTH_STARTED,
                 mBiometricService.mCurrentAuthSession.mState);
@@ -707,9 +772,9 @@
 
     @Test
     public void testLockout_whenAuthenticating_credentialNotAllowed() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         assertEquals(BiometricService.STATE_AUTH_STARTED,
                 mBiometricService.mCurrentAuthSession.mState);
@@ -732,9 +797,9 @@
     @Test
     public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication()
             throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver
                 .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
@@ -755,9 +820,9 @@
 
     @Test
     public void testDismissedReasonNegative_whilePaused_doesntInvokeHalCancel() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -781,9 +846,9 @@
     @Test
     public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws
             Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
@@ -806,9 +871,9 @@
 
     @Test
     public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                true /* requireConfirmation */, false /* allowDeviceCredential */);
+                true /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver.onAuthenticationSucceeded(
                 true /* requireConfirmation */,
@@ -835,9 +900,9 @@
 
     @Test
     public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception {
-        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, null /* authenticators */);
 
         mBiometricService.mInternalReceiver.onAcquired(
                 FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
@@ -851,26 +916,354 @@
                 BiometricService.STATE_AUTH_STARTED);
     }
 
+    @Test
+    public void testCancel_whenAuthenticating() throws Exception {
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */, null /* authenticators */);
+
+        mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
+                TEST_PACKAGE_NAME);
+        waitForIdle();
+
+        // Pretend that the HAL has responded to cancel with ERROR_CANCELED
+        mBiometricService.mInternalReceiver.onError(getCookieForCurrentSession(
+                mBiometricService.mCurrentAuthSession), BiometricAuthenticator.TYPE_FINGERPRINT,
+                BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */);
+        waitForIdle();
+
+        // Hides system dialog and invokes the onError callback
+        verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
+                eq(0 /* vendorCode */));
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+    }
+
+    @Test
+    public void testCanAuthenticate_whenDeviceHasRequestedBiometricStrength() throws Exception {
+        // When only biometric is requested, and sensor is strong enough
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+    }
+
+    @Test
+    public void testCanAuthenticate_whenDeviceDoesNotHaveRequestedBiometricStrength()
+            throws Exception {
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
+
+        // When only biometric is requested, and sensor is not strong enough
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
+        int authenticators = Authenticators.BIOMETRIC_STRONG;
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+
+        // When credential and biometric are requested, and sensor is not strong enough
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+        authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+    }
+
+    @Test
+    public void testCanAuthenticate_onlyCredentialRequested() throws Exception {
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        // Credential requested but not set up
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(false);
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
+                invokeCanAuthenticate(mBiometricService, Authenticators.DEVICE_CREDENTIAL));
+
+        // Credential requested and set up
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, Authenticators.DEVICE_CREDENTIAL));
+    }
+
+    @Test
+    public void testCanAuthenticate_whenNoBiometricsEnrolled() throws Exception {
+        // With credential set up, test the following.
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+                false /* enrolled */);
+
+        // When only biometric is requested
+        int authenticators = Authenticators.BIOMETRIC_STRONG;
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+
+        // When credential and biometric are requested
+        authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+    }
+
+    @Test
+    public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
+        setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+        when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+
+        // When only biometric is requested
+        int authenticators = Authenticators.BIOMETRIC_STRONG;
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+
+        // When credential and biometric are requested
+        authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+    }
+
+    @Test
+    public void testCanAuthenticate_whenNoBiometricSensor() throws Exception {
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        // When only biometric is requested
+        int authenticators = Authenticators.BIOMETRIC_STRONG;
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+
+        // When credential and biometric are requested, and credential is not set up
+        authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+
+        // When credential and biometric are requested, and credential is set up
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+    }
+
+    @Test
+    public void testAuthenticatorActualStrength() {
+        // Tuple of OEM config, updatedStrength, and expectedStrength
+        final int[][] testCases = {
+                // Downgrades to the specified strength
+                {Authenticators.BIOMETRIC_STRONG, Authenticators.BIOMETRIC_WEAK,
+                        Authenticators.BIOMETRIC_WEAK},
+
+                // Cannot be upgraded
+                {Authenticators.BIOMETRIC_WEAK, Authenticators.BIOMETRIC_STRONG,
+                        Authenticators.BIOMETRIC_WEAK},
+
+                // Downgrades to convenience
+                {Authenticators.BIOMETRIC_WEAK, Authenticators.BIOMETRIC_CONVENIENCE,
+                        Authenticators.BIOMETRIC_CONVENIENCE},
+
+                // EMPTY_SET does not modify specified strength
+                {Authenticators.BIOMETRIC_WEAK, Authenticators.EMPTY_SET,
+                        Authenticators.BIOMETRIC_WEAK},
+        };
+
+        for (int i = 0; i < testCases.length; i++) {
+            final BiometricService.AuthenticatorWrapper authenticator =
+                    new BiometricService.AuthenticatorWrapper(0 /* id */,
+                            BiometricAuthenticator.TYPE_FINGERPRINT,
+                            testCases[i][0],
+                            null /* impl */);
+            authenticator.updateStrength(testCases[i][1]);
+            assertEquals(testCases[i][2], authenticator.getActualStrength());
+        }
+    }
+
+    @Test
+    public void testWithDowngradedAuthenticator() throws Exception {
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        final int testId = 0;
+
+        when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+                .thenReturn(true);
+        when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
+        mBiometricService.mImpl.registerAuthenticator(testId /* id */,
+                BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+                mFingerprintAuthenticator);
+
+        // Downgrade the authenticator
+        for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) {
+            if (wrapper.id == testId) {
+                wrapper.updateStrength(Authenticators.BIOMETRIC_WEAK);
+            }
+        }
+
+        // STRONG-only auth is not available
+        int authenticators = Authenticators.BIOMETRIC_STRONG;
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                authenticators);
+        waitForIdle();
+        verify(mReceiver1).onError(
+                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+                eq(0) /* vendorCode */);
+
+        // Request for weak auth works
+        resetReceiver();
+        authenticators = Authenticators.BIOMETRIC_WEAK;
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+        invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */,
+                authenticators);
+        waitForIdle();
+        verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+                eq(mBiometricService.mCurrentAuthSession.mBundle),
+                any(IBiometricServiceReceiverInternal.class),
+                eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */),
+                anyBoolean() /* requireConfirmation */,
+                anyInt() /* userId */,
+                eq(TEST_PACKAGE_NAME));
+
+        // Requesting strong and credential, when credential is setup
+        resetReceiver();
+        authenticators = Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL;
+        when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
+        assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+                invokeCanAuthenticate(mBiometricService, authenticators));
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+                false /* requireConfirmation */,
+                authenticators);
+        waitForIdle();
+        assertTrue(Utils.isDeviceCredentialAllowed(mBiometricService.mCurrentAuthSession.mBundle));
+        verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+                eq(mBiometricService.mCurrentAuthSession.mBundle),
+                any(IBiometricServiceReceiverInternal.class),
+                eq(BiometricAuthenticator.TYPE_NONE /* biometricModality */),
+                anyBoolean() /* requireConfirmation */,
+                anyInt() /* userId */,
+                eq(TEST_PACKAGE_NAME));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRegistrationWithDuplicateId_throwsIllegalStateException() throws Exception {
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        mBiometricService.mImpl.registerAuthenticator(
+                0 /* id */, 2 /* modality */, 15 /* strength */,
+                mFingerprintAuthenticator);
+        mBiometricService.mImpl.registerAuthenticator(
+                0 /* id */, 2 /* modality */, 15 /* strength */,
+                mFingerprintAuthenticator);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRegistrationWithUnknownId_throwsIllegalStateException() throws Exception {
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        mBiometricService.mImpl.registerAuthenticator(
+                100 /* id */, 2 /* modality */, 15 /* strength */,
+                mFingerprintAuthenticator);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testRegistrationWithUnsupportedStrength_throwsIllegalStateException()
+            throws Exception {
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        // Only STRONG and WEAK are supported. Let's enforce that CONVENIENCE cannot be
+        // registered. If there is a compelling reason, we can remove this constraint.
+        mBiometricService.mImpl.registerAuthenticator(
+                0 /* id */, 2 /* modality */,
+                Authenticators.BIOMETRIC_CONVENIENCE /* strength */,
+                mFingerprintAuthenticator);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException()
+            throws Exception {
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        mBiometricService.mImpl.registerAuthenticator(
+                0 /* id */, 2 /* modality */,
+                Authenticators.BIOMETRIC_STRONG /* strength */,
+                null /* authenticator */);
+    }
+
+    @Test
+    public void testRegistrationHappyPath_isOk() throws Exception {
+        // This is being tested in many of the other cases, but here's the base case.
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        for (String s : mInjector.getConfiguration(null)) {
+            SensorConfig config = new SensorConfig(s);
+            mBiometricService.mImpl.registerAuthenticator(config.mId, config.mModality,
+                    config.mStrength, mFingerprintAuthenticator);
+        }
+    }
+
     // Helper methods
 
-    private void setupAuthForOnly(int modality) throws RemoteException {
+    private int invokeCanAuthenticate(BiometricService service, int authenticators)
+            throws Exception {
+        return service.mImpl.canAuthenticate(TEST_PACKAGE_NAME, 0 /* userId */, authenticators);
+    }
+
+    private void setupAuthForOnly(int modality, int strength) throws Exception {
+        setupAuthForOnly(modality, strength, true /* enrolled */);
+    }
+
+    // TODO: Reconcile the registration strength with the injector
+    private void setupAuthForOnly(int modality, int strength, boolean enrolled) throws Exception {
         mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
         when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
 
-        if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
-            when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+        if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+            when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+                    .thenReturn(enrolled);
             when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
-            mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG, modality,
+            mBiometricService.mImpl.registerAuthenticator(0 /* id */, modality, strength,
                     mFingerprintAuthenticator);
-        } else if (modality == BiometricAuthenticator.TYPE_FACE) {
-            when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+        }
+
+        if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+            when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(enrolled);
             when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
-            mBiometricService.mImpl.registerAuthenticator(0 /* id */, STRENGTH_STRONG, modality,
+            mBiometricService.mImpl.registerAuthenticator(1 /* id */, modality, strength,
                     mFaceAuthenticator);
-        } else {
-            fail("Unknown modality: " + modality);
+        }
+    }
+
+    // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
+    // all tests.
+    private void setupAuthForMultiple(int[] modalities, int[] strengths) throws RemoteException {
+        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService.onStart();
+
+        when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+
+        assertEquals(modalities.length, strengths.length);
+
+        for (int i = 0; i < modalities.length; i++) {
+            final int modality = modalities[i];
+            final int strength = strengths[i];
+
+            if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
+                when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
+                        .thenReturn(true);
+                when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
+                mBiometricService.mImpl.registerAuthenticator(0 /* id */, modality, strength,
+                        mFingerprintAuthenticator);
+            }
+
+            if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+                when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+                when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
+                mBiometricService.mImpl.registerAuthenticator(1 /* id */, modality, strength,
+                        mFaceAuthenticator);
+            }
         }
     }
 
@@ -885,9 +1278,9 @@
 
     private void invokeAuthenticateAndStart(IBiometricService.Stub service,
             IBiometricServiceReceiver receiver, boolean requireConfirmation,
-            boolean allowDeviceCredential) throws Exception {
+            Integer authenticators) throws Exception {
         // Request auth, creates a pending session
-        invokeAuthenticate(service, receiver, requireConfirmation, allowDeviceCredential);
+        invokeAuthenticate(service, receiver, requireConfirmation, authenticators);
         waitForIdle();
 
         startPendingAuthSession(mBiometricService);
@@ -908,23 +1301,24 @@
 
     private static void invokeAuthenticate(IBiometricService.Stub service,
             IBiometricServiceReceiver receiver, boolean requireConfirmation,
-            boolean allowDeviceCredential) throws Exception {
+            Integer authenticators) throws Exception {
         service.authenticate(
                 new Binder() /* token */,
                 0 /* sessionId */,
                 0 /* userId */,
                 receiver,
                 TEST_PACKAGE_NAME /* packageName */,
-                createTestBiometricPromptBundle(requireConfirmation, allowDeviceCredential));
+                createTestBiometricPromptBundle(requireConfirmation, authenticators));
     }
 
-    private static Bundle createTestBiometricPromptBundle(boolean requireConfirmation,
-            boolean allowDeviceCredential) {
+    private static Bundle createTestBiometricPromptBundle(
+            boolean requireConfirmation,
+            Integer authenticators) {
         final Bundle bundle = new Bundle();
         bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, requireConfirmation);
 
-        if (allowDeviceCredential) {
-            bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, true);
+        if (authenticators != null) {
+            bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
         }
         return bundle;
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/OWNERS b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
new file mode 100644
index 0000000..8765c9a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+kchyn@google.com
+jaggies@google.com
+curtislb@google.com
+ilyamaty@google.com
+joshmccloskey@google.com
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
new file mode 100644
index 0000000..abe39f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.biometrics;
+
+import static android.hardware.biometrics.BiometricManager.Authenticators;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertNull;
+
+import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class UtilsTest {
+
+    @Test
+    public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() {
+        final boolean allowDeviceCredential = false;
+        final @Authenticators.Types int authenticators =
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+        final Bundle bundle = new Bundle();
+
+        bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
+        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+        Utils.combineAuthenticatorBundles(bundle);
+
+        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+        assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+    }
+
+    @Test
+    public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andKeyAuthenticators() {
+        final @Authenticators.Types int authenticators =
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK;
+        final Bundle bundle = new Bundle();
+
+        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+        Utils.combineAuthenticatorBundles(bundle);
+
+        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+        assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED), authenticators);
+    }
+
+    @Test
+    public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andNoKeyAuthenticators() {
+        final boolean allowDeviceCredential = true;
+        final Bundle bundle = new Bundle();
+
+        bundle.putBoolean(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL, allowDeviceCredential);
+        Utils.combineAuthenticatorBundles(bundle);
+
+        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+        assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+    }
+
+    @Test
+    public void testCombineAuthenticatorBundles_withNoKeyDeviceCredential_andNoKeyAuthenticators() {
+        final Bundle bundle = new Bundle();
+
+        Utils.combineAuthenticatorBundles(bundle);
+
+        assertNull(bundle.get(BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL));
+        assertEquals(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED),
+                Authenticators.BIOMETRIC_WEAK);
+    }
+
+    @Test
+    public void testIsDeviceCredentialAllowed_withIntegerFlags() {
+        int authenticators = 0;
+        assertFalse(Utils.isDeviceCredentialAllowed(authenticators));
+
+        authenticators |= Authenticators.DEVICE_CREDENTIAL;
+        assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+
+        authenticators |= Authenticators.BIOMETRIC_WEAK;
+        assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+    }
+
+    @Test
+    public void testIsDeviceCredentialAllowed_withBundle() {
+        Bundle bundle = new Bundle();
+        assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+
+        int authenticators = 0;
+        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+        assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+
+        authenticators |= Authenticators.DEVICE_CREDENTIAL;
+        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+        assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+
+        authenticators |= Authenticators.BIOMETRIC_WEAK;
+        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+        assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+    }
+
+    @Test
+    public void testGetBiometricStrength_removeUnrelatedBits() {
+        // BIOMETRIC_MIN_STRENGTH uses all of the allowed bits for biometric strength, so any other
+        // bits aside from these should be clipped off.
+
+        int authenticators = Integer.MAX_VALUE;
+        assertEquals(Authenticators.BIOMETRIC_WEAK,
+                Utils.getPublicBiometricStrength(authenticators));
+
+        Bundle bundle = new Bundle();
+        bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+        assertEquals(Authenticators.BIOMETRIC_WEAK, Utils.getPublicBiometricStrength(bundle));
+    }
+
+    @Test
+    public void testIsBiometricAllowed() {
+        // Only the lowest 8 bits (BIOMETRIC_WEAK mask) are allowed to integrate with the
+        // Biometric APIs
+        Bundle bundle = new Bundle();
+        for (int i = 0; i <= 7; i++) {
+            int authenticators = 1 << i;
+            bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+            assertTrue(Utils.isBiometricAllowed(bundle));
+        }
+
+        // The rest of the bits are not allowed to integrate with the public APIs
+        for (int i = 8; i < 32; i++) {
+            int authenticators = 1 << i;
+            bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
+            assertFalse(Utils.isBiometricAllowed(bundle));
+        }
+    }
+
+    @Test
+    public void testIsValidAuthenticatorConfig() {
+        assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.EMPTY_SET));
+
+        assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_STRONG));
+
+        assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_WEAK));
+
+        assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL));
+
+        assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+                | Authenticators.BIOMETRIC_STRONG));
+
+        assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+                | Authenticators.BIOMETRIC_WEAK));
+
+        assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE));
+
+        assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE
+                | Authenticators.DEVICE_CREDENTIAL));
+
+        assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MAX_STRENGTH));
+
+        assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MIN_STRENGTH));
+
+        // The rest of the bits are not allowed to integrate with the public APIs
+        for (int i = 8; i < 32; i++) {
+            final int authenticator = 1 << i;
+            if (authenticator == Authenticators.DEVICE_CREDENTIAL) {
+                continue;
+            }
+            assertFalse(Utils.isValidAuthenticatorConfig(1 << i));
+        }
+    }
+
+    @Test
+    public void testIsAtLeastStrength() {
+        int sensorStrength = Authenticators.BIOMETRIC_STRONG;
+        int requestedStrength = Authenticators.BIOMETRIC_WEAK;
+        assertTrue(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+
+        requestedStrength = Authenticators.BIOMETRIC_STRONG;
+        assertTrue(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+
+        sensorStrength = Authenticators.BIOMETRIC_WEAK;
+        requestedStrength = Authenticators.BIOMETRIC_STRONG;
+        assertFalse(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+
+        requestedStrength = Authenticators.BIOMETRIC_WEAK;
+        assertTrue(Utils.isAtLeastStrength(sensorStrength, requestedStrength));
+    }
+
+    @Test
+    public void testBiometricConstantsConversion() {
+        final int[][] testCases = {
+                {BiometricConstants.BIOMETRIC_SUCCESS,
+                        BiometricManager.BIOMETRIC_SUCCESS},
+                {BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS,
+                        BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED},
+                {BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
+                        BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED},
+                {BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+                        BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE},
+                {BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT,
+                        BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE}
+        };
+
+        for (int i = 0; i < testCases.length; i++) {
+            assertEquals(testCases[i][1],
+                    Utils.biometricConstantsToBiometricManager(testCases[i][0]));
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index 7267976..cb99c11 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -22,15 +22,13 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compat.annotation.Change;
-import com.android.compat.annotation.XmlWriter;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.UUID;
 
 @RunWith(AndroidJUnit4.class)
@@ -50,18 +48,10 @@
         return dir;
     }
 
-    private void writeChangesToFile(Change[] changes, File f) {
-        XmlWriter writer = new XmlWriter();
-        for (Change change: changes) {
-            writer.addChange(change);
-        }
-        try {
-            f.createNewFile();
-            writer.write(new FileOutputStream(f));
-        } catch (IOException e) {
-            throw new RuntimeException(
-                    "Encountered an error while writing compat config file", e);
-        }
+    private void writeToFile(File dir, String filename, String content) throws IOException {
+        OutputStream os = new FileOutputStream(new File(dir, filename));
+        os.write(content.getBytes());
+        os.close();
     }
 
     @Test
@@ -173,13 +163,15 @@
     }
 
     @Test
-    public void testReadConfig() {
-        Change[] changes = {new Change(1234L, "MY_CHANGE1", false, 2, null), new Change(1235L,
-                "MY_CHANGE2", true, null, "description"), new Change(1236L, "MY_CHANGE3", false,
-                null, "")};
+    public void testReadConfig() throws IOException {
+        String configXml = "<config>"
+                + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />"
+                + "<compat-change id=\"1235\" name=\"MY_CHANGE2\" disabled=\"true\" />"
+                + "<compat-change id=\"1236\" name=\"MY_CHANGE3\" />"
+                + "</config>";
 
         File dir = createTempDir();
-        writeChangesToFile(changes, new File(dir.getPath() + "/platform_compat_config.xml"));
+        writeToFile(dir, "platform_compat_config.xml", configXml);
 
         CompatConfig pc = new CompatConfig();
         pc.initConfigFromLib(dir);
@@ -191,17 +183,18 @@
     }
 
     @Test
-    public void testReadConfigMultipleFiles() {
-        Change[] changes1 = {new Change(1234L, "MY_CHANGE1", false, 2, null)};
-        Change[] changes2 = {new Change(1235L, "MY_CHANGE2", true, null, ""), new Change(1236L,
-                "MY_CHANGE3", false, null, null)};
+    public void testReadConfigMultipleFiles() throws IOException {
+        String configXml1 = "<config>"
+                + "<compat-change id=\"1234\" name=\"MY_CHANGE1\" enableAfterTargetSdk=\"2\" />"
+                + "</config>";
+        String configXml2 = "<config>"
+                + "<compat-change id=\"1235\" name=\"MY_CHANGE2\" disabled=\"true\" />"
+                + "<compat-change id=\"1236\" name=\"MY_CHANGE3\" />"
+                + "</config>";
 
         File dir = createTempDir();
-        writeChangesToFile(changes1,
-                new File(dir.getPath() + "/libcore_platform_compat_config.xml"));
-        writeChangesToFile(changes2,
-                new File(dir.getPath() + "/frameworks_platform_compat_config.xml"));
-
+        writeToFile(dir, "libcore_platform_compat_config.xml", configXml1);
+        writeToFile(dir, "frameworks_platform_compat_config.xml", configXml2);
 
         CompatConfig pc = new CompatConfig();
         pc.initConfigFromLib(dir);
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 f54f885..f97c887 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1162,7 +1162,8 @@
                 MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
 
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
-                eq(UserHandle.USER_SYSTEM), eq(null),
+                eq(UserHandle.USER_SYSTEM),
+                eq(null),
                 eq(UserManagerInternal.OWNER_TYPE_DEVICE_OWNER));
 
         verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
@@ -1966,7 +1967,6 @@
         // TODO Make sure restrictions are written to the file.
     }
 
-    // TODO: (b/138709470) test addUserRestriction as PO of an organization-owned device
     public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
         final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
         final int MANAGED_PROFILE_ADMIN_UID =
@@ -1979,16 +1979,26 @@
         when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID))
                 .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
 
+        parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
+        verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(MANAGED_PROFILE_USER_ID),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME),
+                eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
+        reset(getServices().userManagerInternal);
+
+        parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
+        reset(getServices().userManagerInternal);
+
         parentDpm.setCameraDisabled(admin1, true);
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
-                eq(UserHandle.USER_SYSTEM),
+                eq(MANAGED_PROFILE_USER_ID),
                 MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
                 eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
         reset(getServices().userManagerInternal);
 
         parentDpm.setCameraDisabled(admin1, false);
         verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
-                eq(UserHandle.USER_SYSTEM),
+                eq(MANAGED_PROFILE_USER_ID),
                 MockUtils.checkUserRestrictions(),
                 eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
         reset(getServices().userManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 7a2350e..919a3f6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -35,6 +35,7 @@
 import android.app.timezonedetector.TimeZoneDetector;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
@@ -162,6 +163,8 @@
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(realContext.getPackageManager());
+        when(packageManagerInternal.getSystemUiServiceComponent()).thenReturn(
+                new ComponentName("com.android.systemui", ".Service"));
 
         contentResolver = new MockContentResolver();
         contentResolver.addProvider("telephony", new MockContentProvider(realContext) {
diff --git a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
new file mode 100644
index 0000000..0f915db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.om
+
+import org.mockito.Answers
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.stubbing.Answer
+import org.mockito.stubbing.Stubber
+
+// TODO(chiuwinson): Move this entire file to a shared utility module
+// TODO(b/135203078): De-dupe utils added for overlays vs package refactor
+object MockitoUtils {
+    val ANSWER_THROWS = Answer<Any?> {
+        when (val name = it.method.name) {
+            "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+            else -> {
+                val arguments = it.arguments
+                        ?.takeUnless { it.isEmpty() }
+                        ?.joinToString()
+                        ?.let {
+                            "with $it"
+                        }
+                        .orEmpty()
+
+                throw UnsupportedOperationException("${it.mock::class.java.simpleName}#$name " +
+                        "$arguments should not be called")
+            }
+        }
+    }
+}
+
+inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
+
+fun <Type> Stubber.whenever(mock: Type) = Mockito.`when`(mock)
+fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock)
+
+@Suppress("UNCHECKED_CAST")
+fun <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Any?) =
+        Mockito.`when`(mock).thenAnswer { block(it) }
+
+fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { }
+
+inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T {
+    val swappingAnswer = object : Answer<Any?> {
+        var delegate: Answer<*> = Answers.RETURNS_DEFAULTS
+
+        override fun answer(invocation: InvocationOnMock?): Any? {
+            return delegate.answer(invocation)
+        }
+    }
+
+    return Mockito.mock(T::class.java, swappingAnswer).apply(block)
+            .also {
+                // To allow when() usage inside block, only swap to throwing afterwards
+                swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS
+            }
+}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
new file mode 100644
index 0000000..ef12948
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.om
+
+import android.content.pm.parsing.AndroidPackage
+import android.net.Uri
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.testng.Assert.assertThrows
+
+@RunWith(Parameterized::class)
+class OverlayReferenceMapperTests {
+
+    companion object {
+        private const val TARGET_PACKAGE_NAME = "com.test.target"
+        private const val OVERLAY_PACKAGE_NAME = "com.test.overlay"
+        private const val ACTOR_PACKAGE_NAME = "com.test.actor"
+        private const val ACTOR_NAME = "overlay://test/actorName"
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "deferRebuild {0}")
+        fun parameters() = arrayOf(true, false)
+    }
+
+    private lateinit var mapper: OverlayReferenceMapper
+
+    @JvmField
+    @Parameterized.Parameter(0)
+    var deferRebuild = false
+
+    @Before
+    fun initMapper() {
+        mapper = mapper()
+    }
+
+    @Test
+    fun targetWithOverlay() {
+        val target = mockTarget()
+        val overlay = mockOverlay()
+        val existing = mapper.addInOrder(overlay)
+        assertEmpty()
+        mapper.addInOrder(target, existing = existing)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
+        mapper.remove(target)
+        assertEmpty()
+    }
+
+    @Test
+    fun targetWithMultipleOverlays() {
+        val target = mockTarget()
+        val overlay0 = mockOverlay(0)
+        val overlay1 = mockOverlay(1)
+        mapper = mapper(
+                overlayToTargetToOverlayables = mapOf(
+                        overlay0.packageName to mapOf(
+                                target.packageName to target.overlayables.keys
+                        ),
+                        overlay1.packageName to mapOf(
+                                target.packageName to target.overlayables.keys
+                        )
+                )
+        )
+        val existing = mapper.addInOrder(overlay0, overlay1)
+        assertEmpty()
+        mapper.addInOrder(target, existing = existing)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay0, overlay1))
+        mapper.remove(overlay0)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay1))
+        mapper.remove(target)
+        assertEmpty()
+    }
+
+    @Test
+    fun targetWithoutOverlay() {
+        val target = mockTarget()
+        mapper.addInOrder(target)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+        mapper.remove(target)
+        assertEmpty()
+    }
+
+    @Test
+    fun overlayWithTarget() {
+        val target = mockTarget()
+        val overlay = mockOverlay()
+        val existing = mapper.addInOrder(target)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+        mapper.addInOrder(overlay, existing = existing)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
+        mapper.remove(overlay)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+    }
+
+    @Test
+    fun overlayWithMultipleTargets() {
+        val target0 = mockTarget(0)
+        val target1 = mockTarget(1)
+        val overlay = mockOverlay()
+        mapper = mapper(
+                overlayToTargetToOverlayables = mapOf(
+                        overlay.packageName to mapOf(
+                                target0.packageName to target0.overlayables.keys,
+                                target1.packageName to target1.overlayables.keys
+                        )
+                )
+        )
+        mapper.addInOrder(target0, target1, overlay)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay))
+        mapper.remove(target0)
+        assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay))
+        mapper.remove(target1)
+        assertEmpty()
+    }
+
+    @Test
+    fun overlayWithoutTarget() {
+        val overlay = mockOverlay()
+        mapper.addInOrder(overlay)
+        // An overlay can only have visibility exposed through its target
+        assertEmpty()
+        mapper.remove(overlay)
+        assertEmpty()
+    }
+
+    private fun OverlayReferenceMapper.addInOrder(
+        vararg pkgs: AndroidPackage,
+        existing: MutableMap<String, AndroidPackage> = mutableMapOf()
+    ) = pkgs.fold(existing) { map, pkg ->
+        addPkg(pkg, map)
+        map[pkg.packageName] = pkg
+        return@fold map
+    }
+
+    private fun OverlayReferenceMapper.remove(pkg: AndroidPackage) = removePkg(pkg.packageName)
+
+    private fun assertMapping(vararg pairs: Pair<String, Set<AndroidPackage>>) {
+        val expected = pairs.associate { it }
+                .mapValues { pair -> pair.value.map { it.packageName }.toSet() }
+
+        // This validates the API exposed for querying the relationships
+        expected.forEach { (actorPkgName, expectedPkgNames) ->
+            expectedPkgNames.forEach { expectedPkgName ->
+                if (deferRebuild) {
+                    assertThrows(IllegalStateException::class.java) {
+                        mapper.isValidActor(expectedPkgName, actorPkgName)
+                    }
+                    mapper.rebuildIfDeferred()
+                    deferRebuild = false
+                }
+
+                assertThat(mapper.isValidActor(expectedPkgName, actorPkgName)).isTrue()
+            }
+        }
+
+        // This asserts no other relationships are defined besides those tested above
+        assertThat(mapper.actorPkgToPkgs).containsExactlyEntriesIn(expected)
+    }
+
+    private fun assertEmpty() = assertMapping()
+
+    private fun mapper(
+        namedActors: Map<String, Map<String, String>> = Uri.parse(ACTOR_NAME).run {
+            mapOf(authority!! to mapOf(pathSegments.first() to ACTOR_PACKAGE_NAME))
+        },
+        overlayToTargetToOverlayables: Map<String, Map<String, Set<String>>> = mapOf(
+                mockOverlay().packageName to mapOf(
+                        mockTarget().run { packageName to overlayables.keys }
+                )
+        )
+    ) = OverlayReferenceMapper(deferRebuild, object : OverlayReferenceMapper.Provider {
+        override fun getActorPkg(actor: String?) =
+                OverlayActorEnforcer.getPackageNameForActor(actor, namedActors).first
+
+        override fun getTargetToOverlayables(pkg: AndroidPackage) =
+                overlayToTargetToOverlayables[pkg.packageName] ?: emptyMap()
+    })
+
+    private fun mockTarget(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
+        whenever(packageName) { "$TARGET_PACKAGE_NAME$increment" }
+        whenever(overlayables) { mapOf("overlayableName$increment" to ACTOR_NAME) }
+        whenever(toString()) { "Package{$packageName}" }
+    }
+
+    private fun mockOverlay(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
+        whenever(packageName) { "$OVERLAY_PACKAGE_NAME$increment" }
+        whenever(overlayables) { emptyMap<String, String>() }
+        whenever(toString()) { "Package{$packageName}" }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 4fc625a..82bbdcb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -35,6 +35,11 @@
 import android.os.Build;
 import android.os.Process;
 import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+
+import com.android.server.om.OverlayReferenceMapper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,11 +48,18 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
 @RunWith(JUnit4.class)
 public class AppsFilterTest {
 
     private static final int DUMMY_CALLING_UID = 10345;
     private static final int DUMMY_TARGET_UID = 10556;
+    private static final int DUMMY_ACTOR_UID = 10656;
+    private static final int DUMMY_OVERLAY_UID = 10756;
+    private static final int DUMMY_ACTOR_TWO_UID = 10856;
 
     @Mock
     AppsFilter.FeatureConfig mFeatureConfigMock;
@@ -117,7 +129,7 @@
     @Test
     public void testSystemReadyPropogates() throws Exception {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
         appsFilter.onSystemReady();
         verify(mFeatureConfigMock).onSystemReady();
     }
@@ -125,7 +137,8 @@
     @Test
     public void testQueriesAction_FilterMatches() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package", new IntentFilter("TEST_ACTION")), DUMMY_TARGET_UID);
@@ -138,7 +151,8 @@
     @Test
     public void testQueriesAction_NoMatchingAction_Filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -151,7 +165,8 @@
     @Test
     public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -169,7 +184,8 @@
     @Test
     public void testNoQueries_Filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -182,7 +198,8 @@
     @Test
     public void testForceQueryable_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                         pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID);
@@ -195,7 +212,8 @@
     @Test
     public void testForceQueryableByDevice_SystemCaller_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID,
@@ -209,7 +227,8 @@
     @Test
     public void testForceQueryableByDevice_NonSystemCaller_Filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{"com.some.package"}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -224,7 +243,8 @@
     public void testSystemQueryable_DoesntFilter() {
         final AppsFilter appsFilter =
                 new AppsFilter(mFeatureConfigMock, new String[]{},
-                        true /* system force queryable */);
+                        true /* system force queryable */, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID,
@@ -238,7 +258,8 @@
     @Test
     public void testQueriesPackage_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -253,7 +274,8 @@
         when(mFeatureConfigMock.packageIsEnabled(any(AndroidPackage.class)))
                 .thenReturn(false);
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(
                 appsFilter, pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -266,20 +288,22 @@
     @Test
     public void testSystemUid_DoesntFilter() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
 
         assertFalse(appsFilter.shouldFilterApplication(0, null, target, 0));
-        assertFalse(appsFilter.shouldFilterApplication(
-                Process.FIRST_APPLICATION_UID - 1, null, target, 0));
+        assertFalse(appsFilter.shouldFilterApplication(Process.FIRST_APPLICATION_UID - 1,
+                null, target, 0));
     }
 
     @Test
     public void testNonSystemUid_NoCallingSetting_Filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = simulateAddPackage(appsFilter,
                 pkg("com.some.package"), DUMMY_TARGET_UID);
@@ -290,7 +314,8 @@
     @Test
     public void testNoTargetPackage_filters() {
         final AppsFilter appsFilter =
-                new AppsFilter(mFeatureConfigMock, new String[]{}, false);
+                new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+        appsFilter.onSystemReady();
 
         PackageSetting target = new PackageSettingBuilder()
                 .setName("com.some.package")
@@ -304,6 +329,127 @@
         assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
     }
 
+    @Test
+    public void testActsOnTargetOfOverlay() {
+        final String actorName = "overlay://test/actorName";
+
+        ParsingPackage target = pkg("com.some.package.target")
+                .addOverlayable("overlayableName", actorName);
+        ParsingPackage overlay = pkg("com.some.package.overlay")
+                .setIsOverlay(true)
+                .setOverlayTarget(target.getPackageName())
+                .setOverlayTargetName("overlayableName");
+        ParsingPackage actor = pkg("com.some.package.actor");
+
+        final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false,
+                new OverlayReferenceMapper.Provider() {
+                    @Nullable
+                    @Override
+                    public String getActorPkg(String actorString) {
+                        if (actorName.equals(actorString)) {
+                            return actor.getPackageName();
+                        }
+                        return null;
+                    }
+
+                    @NonNull
+                    @Override
+                    public Map<String, Set<String>> getTargetToOverlayables(
+                            @NonNull AndroidPackage pkg) {
+                        if (overlay.getPackageName().equals(pkg.getPackageName())) {
+                            Map<String, Set<String>> map = new ArrayMap<>();
+                            Set<String> set = new ArraySet<>();
+                            set.add(overlay.getOverlayTargetName());
+                            map.put(overlay.getOverlayTarget(), set);
+                            return map;
+                        }
+                        return Collections.emptyMap();
+                    }
+                });
+        appsFilter.onSystemReady();
+
+        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID);
+        PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID);
+        PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_UID);
+
+        // Actor can see both target and overlay
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting,
+                targetSetting, 0));
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_UID, actorSetting,
+                overlaySetting, 0));
+
+        // But target/overlay can't see each other
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting,
+                overlaySetting, 0));
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting,
+                targetSetting, 0));
+
+        // And can't see the actor
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_TARGET_UID, targetSetting,
+                actorSetting, 0));
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_UID, overlaySetting,
+                actorSetting, 0));
+    }
+
+    @Test
+    public void testActsOnTargetOfOverlayThroughSharedUser() {
+        final String actorName = "overlay://test/actorName";
+
+        ParsingPackage target = pkg("com.some.package.target")
+                .addOverlayable("overlayableName", actorName);
+        ParsingPackage overlay = pkg("com.some.package.overlay")
+                .setIsOverlay(true)
+                .setOverlayTarget(target.getPackageName())
+                .setOverlayTargetName("overlayableName");
+        ParsingPackage actorOne = pkg("com.some.package.actor.one");
+        ParsingPackage actorTwo = pkg("com.some.package.actor.two");
+
+        final AppsFilter appsFilter = new AppsFilter(mFeatureConfigMock, new String[]{}, false,
+                new OverlayReferenceMapper.Provider() {
+                    @Nullable
+                    @Override
+                    public String getActorPkg(String actorString) {
+                        // Only actorOne is mapped as a valid actor
+                        if (actorName.equals(actorString)) {
+                            return actorOne.getPackageName();
+                        }
+                        return null;
+                    }
+
+                    @NonNull
+                    @Override
+                    public Map<String, Set<String>> getTargetToOverlayables(
+                            @NonNull AndroidPackage pkg) {
+                        if (overlay.getPackageName().equals(pkg.getPackageName())) {
+                            Map<String, Set<String>> map = new ArrayMap<>();
+                            Set<String> set = new ArraySet<>();
+                            set.add(overlay.getOverlayTargetName());
+                            map.put(overlay.getOverlayTarget(), set);
+                            return map;
+                        }
+                        return Collections.emptyMap();
+                    }
+                });
+        appsFilter.onSystemReady();
+
+        PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_UID);
+        PackageSetting overlaySetting = simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_UID);
+        PackageSetting actorOneSetting = simulateAddPackage(appsFilter, actorOne, DUMMY_ACTOR_UID);
+        PackageSetting actorTwoSetting = simulateAddPackage(appsFilter, actorTwo,
+                DUMMY_ACTOR_TWO_UID);
+
+        SharedUserSetting actorSharedSetting = new SharedUserSetting("actorSharedUser",
+                actorOneSetting.pkgFlags, actorOneSetting.pkgPrivateFlags);
+        actorSharedSetting.addPackage(actorOneSetting);
+        actorSharedSetting.addPackage(actorTwoSetting);
+
+        // actorTwo can see both target and overlay
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting,
+                targetSetting, 0));
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_TWO_UID, actorSharedSetting,
+                overlaySetting, 0));
+    }
+
     private interface WithSettingBuilder {
         PackageSettingBuilder withBuilder(PackageSettingBuilder builder);
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index b751308..c478ec4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -173,6 +173,7 @@
                 /* createdMillis */ 0L,
                 /* stageDir */ mTmpDir,
                 /* stageCid */ null,
+                /* files */ null,
                 /* prepared */ true,
                 /* committed */ true,
                 /* sealed */ false,  // Setting to true would trigger some PM logic.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 5baeede..2473997 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -22,7 +22,7 @@
 
 import java.io.File;
 
-class PackageSettingBuilder {
+public class PackageSettingBuilder {
     private String mName;
     private String mRealName;
     private String mCodePath;
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
new file mode 100644
index 0000000..5a2ce45
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.audio.common.V2_0.Uuid;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ConversionUtilTest {
+    private static final String TAG = "ConversionUtilTest";
+
+    @Test
+    public void testUuidRoundTrip() {
+        Uuid hidl = new Uuid();
+        hidl.timeLow = 0xFEDCBA98;
+        hidl.timeMid = (short) 0xEDCB;
+        hidl.versionAndTimeHigh = (short) 0xDCBA;
+        hidl.variantAndClockSeqHigh = (short) 0xCBA9;
+        hidl.node = new byte[] { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
+
+        String aidl = ConversionUtil.hidl2aidlUuid(hidl);
+        assertEquals("fedcba98-edcb-dcba-cba9-111213141516", aidl);
+
+        Uuid reconstructed = ConversionUtil.aidl2hidlUuid(aidl);
+        assertEquals(hidl, reconstructed);
+    }
+}
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
new file mode 100644
index 0000000..82f32f8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -0,0 +1,1306 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.audio.common.V2_0.AudioConfig;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
+import android.media.audio.common.AudioChannelMask;
+import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemoryUtil;
+import android.os.HwParcel;
+import android.os.IHwBinder;
+import android.os.IHwInterface;
+import android.os.RemoteException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+@RunWith(Parameterized.class)
+public class SoundTriggerMiddlewareImplTest {
+    private static final String TAG = "SoundTriggerMiddlewareImplTest";
+
+    // We run the test once for every version of the underlying driver.
+    @Parameterized.Parameters
+    public static Object[] data() {
+        return new Object[]{
+                mock(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.class),
+                mock(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.class),
+                mock(android.hardware.soundtrigger.V2_2.ISoundTriggerHw.class),
+                mock(android.hardware.soundtrigger.V2_3.ISoundTriggerHw.class),
+        };
+    }
+
+    @Mock
+    @Parameterized.Parameter
+    public android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
+
+    @Mock
+    private SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider = mock(
+            SoundTriggerMiddlewareImpl.AudioSessionProvider.class);
+
+    private SoundTriggerMiddlewareImpl mService;
+
+    private static ISoundTriggerCallback createCallbackMock() {
+        return mock(ISoundTriggerCallback.Stub.class, Mockito.CALLS_REAL_METHODS);
+    }
+
+    private static SoundModel createGenericSoundModel() {
+        return createSoundModel(SoundModelType.GENERIC);
+    }
+
+    private static SoundModel createSoundModel(int type) {
+        SoundModel model = new SoundModel();
+        model.type = type;
+        model.uuid = "12345678-2345-3456-4567-abcdef987654";
+        model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
+        model.data = new byte[]{91, 92, 93, 94, 95};
+        return model;
+    }
+
+    private static PhraseSoundModel createPhraseSoundModel() {
+        PhraseSoundModel model = new PhraseSoundModel();
+        model.common = createSoundModel(SoundModelType.KEYPHRASE);
+        model.phrases = new Phrase[1];
+        model.phrases[0] = new Phrase();
+        model.phrases[0].id = 123;
+        model.phrases[0].users = new int[]{5, 6, 7};
+        model.phrases[0].locale = "locale";
+        model.phrases[0].text = "text";
+        model.phrases[0].recognitionModes =
+                RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION;
+        return model;
+    }
+
+    private static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties(
+            boolean supportConcurrentCapture) {
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+                new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
+        properties.implementor = "implementor";
+        properties.description = "description";
+        properties.version = 123;
+        properties.uuid = new Uuid();
+        properties.uuid.timeLow = 1;
+        properties.uuid.timeMid = 2;
+        properties.uuid.versionAndTimeHigh = 3;
+        properties.uuid.variantAndClockSeqHigh = 4;
+        properties.uuid.node = new byte[]{5, 6, 7, 8, 9, 10};
+
+        properties.maxSoundModels = 456;
+        properties.maxKeyPhrases = 567;
+        properties.maxUsers = 678;
+        properties.recognitionModes = 789;
+        properties.captureTransition = true;
+        properties.maxBufferMs = 321;
+        properties.concurrentCapture = supportConcurrentCapture;
+        properties.triggerInEvent = true;
+        properties.powerConsumptionMw = 432;
+        return properties;
+    }
+
+    private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+            boolean supportConcurrentCapture) {
+        assertEquals("implementor", properties.implementor);
+        assertEquals("description", properties.description);
+        assertEquals(123, properties.version);
+        assertEquals("00000001-0002-0003-0004-05060708090a", properties.uuid);
+        assertEquals(456, properties.maxSoundModels);
+        assertEquals(567, properties.maxKeyPhrases);
+        assertEquals(678, properties.maxUsers);
+        assertEquals(789, properties.recognitionModes);
+        assertTrue(properties.captureTransition);
+        assertEquals(321, properties.maxBufferMs);
+        assertEquals(supportConcurrentCapture, properties.concurrentCapture);
+        assertTrue(properties.triggerInEvent);
+        assertEquals(432, properties.powerConsumptionMw);
+    }
+
+
+    private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
+            int hwHandle,
+            int status) {
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+                new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent();
+        halEvent.status = status;
+        halEvent.type = SoundModelType.GENERIC;
+        halEvent.model = hwHandle;
+        halEvent.captureAvailable = true;
+        // This field is ignored.
+        halEvent.captureSession = 123;
+        halEvent.captureDelayMs = 234;
+        halEvent.capturePreambleMs = 345;
+        halEvent.triggerInData = true;
+        halEvent.audioConfig = new AudioConfig();
+        halEvent.audioConfig.sampleRateHz = 456;
+        halEvent.audioConfig.channelMask = AudioChannelMask.IN_LEFT;
+        halEvent.audioConfig.format = AudioFormat.MP3;
+        // hwEvent.audioConfig.offloadInfo is irrelevant.
+        halEvent.data.add((byte) 31);
+        halEvent.data.add((byte) 32);
+        halEvent.data.add((byte) 33);
+        return halEvent;
+    }
+
+    private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_1(
+            int hwHandle,
+            int status) {
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+                new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+        halEvent.header = createRecognitionEvent_2_0(hwHandle, status);
+        halEvent.header.data.clear();
+        halEvent.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[]{31, 32, 33});
+        return halEvent;
+    }
+
+    private static void validateRecognitionEvent(RecognitionEvent event, int status) {
+        assertEquals(status, event.status);
+        assertEquals(SoundModelType.GENERIC, event.type);
+        assertTrue(event.captureAvailable);
+        assertEquals(101, event.captureSession);
+        assertEquals(234, event.captureDelayMs);
+        assertEquals(345, event.capturePreambleMs);
+        assertTrue(event.triggerInData);
+        assertEquals(456, event.audioConfig.sampleRateHz);
+        assertEquals(AudioChannelMask.IN_LEFT, event.audioConfig.channelMask);
+        assertEquals(AudioFormat.MP3, event.audioConfig.format);
+    }
+
+    private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_0(
+            int hwHandle, int status) {
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+                new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+        halEvent.common = createRecognitionEvent_2_0(hwHandle, status);
+
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+                new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+        halExtra.id = 123;
+        halExtra.confidenceLevel = 52;
+        halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+                | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+                new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+        halLevel.userId = 31;
+        halLevel.levelPercent = 43;
+        halExtra.levels.add(halLevel);
+        halEvent.phraseExtras.add(halExtra);
+        return halEvent;
+    }
+
+    private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_1(
+            int hwHandle, int status) {
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+                new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+        halEvent.common = createRecognitionEvent_2_1(hwHandle, status);
+
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+                new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+        halExtra.id = 123;
+        halExtra.confidenceLevel = 52;
+        halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+                | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+                new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+        halLevel.userId = 31;
+        halLevel.levelPercent = 43;
+        halExtra.levels.add(halLevel);
+        halEvent.phraseExtras.add(halExtra);
+        return halEvent;
+    }
+
+    private static void validatePhraseRecognitionEvent(PhraseRecognitionEvent event, int status) {
+        validateRecognitionEvent(event.common, status);
+
+        assertEquals(1, event.phraseExtras.length);
+        assertEquals(123, event.phraseExtras[0].id);
+        assertEquals(52, event.phraseExtras[0].confidenceLevel);
+        assertEquals(RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER,
+                event.phraseExtras[0].recognitionModes);
+        assertEquals(1, event.phraseExtras[0].levels.length);
+        assertEquals(31, event.phraseExtras[0].levels[0].userId);
+        assertEquals(43, event.phraseExtras[0].levels[0].levelPercent);
+    }
+
+    private void initService(boolean supportConcurrentCapture) throws RemoteException {
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+                    createDefaultProperties(
+                            supportConcurrentCapture);
+            ((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
+                    0)).onValues(0,
+                    properties);
+            return null;
+        }).when(mHalDriver).getProperties(any());
+        mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+    }
+
+    private int 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);
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+                    invocation.getArgument(1);
+            int callbackCookie = invocation.getArgument(2);
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadSoundModelCallback
+                    resultCallback = invocation.getArgument(3);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, hwHandle);
+
+            // This is the async mCallback that comes after.
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+                    new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+            modelEvent.status =
+                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+            modelEvent.model = hwHandle;
+            callback.soundModelCallback(modelEvent, callbackCookie);
+            return null;
+        }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+        when(mAudioSessionProvider.acquireSession()).thenReturn(
+                new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+        int handle = module.loadModel(model);
+        verify(mHalDriver).loadSoundModel(any(), any(), anyInt(), any());
+        verify(mAudioSessionProvider).acquireSession();
+
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel hidlModel =
+                modelCaptor.getValue();
+        assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+                hidlModel.type);
+        assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.uuid));
+        assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
+        assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
+
+        return handle;
+    }
+
+    private int 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);
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+                    invocation.getArgument(1);
+            int callbackCookie = invocation.getArgument(2);
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback
+                    resultCallback = invocation.getArgument(3);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, hwHandle);
+
+            // This is the async mCallback that comes after.
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+                    new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+            modelEvent.header.status =
+                    android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+            modelEvent.header.model = hwHandle;
+            callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+            return null;
+        }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+        when(mAudioSessionProvider.acquireSession()).thenReturn(
+                new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+        int handle = module.loadModel(model);
+        verify(driver).loadSoundModel_2_1(any(), any(), anyInt(), any());
+        verify(mAudioSessionProvider).acquireSession();
+
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel hidlModel =
+                modelCaptor.getValue();
+        assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+                hidlModel.header.type);
+        assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.uuid));
+        assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.vendorUuid));
+        assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+                HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
+
+        return handle;
+    }
+
+    private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+            return loadGenericModel_2_1(module, hwHandle);
+        } else {
+            return loadGenericModel_2_0(module, hwHandle);
+        }
+    }
+
+    private int 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);
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+                    invocation.getArgument(
+                            1);
+            int callbackCookie = invocation.getArgument(2);
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadPhraseSoundModelCallback
+                    resultCallback =
+                    invocation.getArgument(
+                            3);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, hwHandle);
+
+            // This is the async mCallback that comes after.
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+                    new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+            modelEvent.status =
+                    android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+            modelEvent.model = hwHandle;
+            callback.soundModelCallback(modelEvent, callbackCookie);
+            return null;
+        }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+        when(mAudioSessionProvider.acquireSession()).thenReturn(
+                new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+        int handle = module.loadPhraseModel(model);
+        verify(mHalDriver).loadPhraseSoundModel(any(), any(), anyInt(), any());
+        verify(mAudioSessionProvider).acquireSession();
+
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel hidlModel =
+                modelCaptor.getValue();
+
+        // Validate common part.
+        assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+                hidlModel.common.type);
+        assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.uuid));
+        assertEquals(model.common.vendorUuid,
+                ConversionUtil.hidl2aidlUuid(hidlModel.common.vendorUuid));
+        assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.common.data.toArray());
+
+        // Validate phrase part.
+        assertEquals(1, hidlModel.phrases.size());
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Phrase hidlPhrase =
+                hidlModel.phrases.get(0);
+        assertEquals(123, hidlPhrase.id);
+        assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+        assertEquals("locale", hidlPhrase.locale);
+        assertEquals("text", hidlPhrase.text);
+        assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+                        | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+                hidlPhrase.recognitionModes);
+
+        return handle;
+    }
+
+    private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
+            throws RemoteException {
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+        PhraseSoundModel model = createPhraseSoundModel();
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
+                modelCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+                    invocation.getArgument(
+                            1);
+            int callbackCookie = invocation.getArgument(2);
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback
+                    resultCallback =
+                    invocation.getArgument(
+                            3);
+
+            // This is the return of this method.
+            resultCallback.onValues(0, hwHandle);
+
+            // This is the async mCallback that comes after.
+            android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+                    new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+            modelEvent.header.status =
+                    android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+            modelEvent.header.model = hwHandle;
+            callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+            return null;
+        }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+        when(mAudioSessionProvider.acquireSession()).thenReturn(
+                new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+        int handle = module.loadPhraseModel(model);
+        verify(driver).loadPhraseSoundModel_2_1(any(), any(), anyInt(), any());
+        verify(mAudioSessionProvider).acquireSession();
+
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel hidlModel =
+                modelCaptor.getValue();
+
+        // Validate common part.
+        assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+                hidlModel.common.header.type);
+        assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.header.uuid));
+        assertEquals(model.common.vendorUuid,
+                ConversionUtil.hidl2aidlUuid(hidlModel.common.header.vendorUuid));
+        assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+                HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.common.data));
+
+        // Validate phrase part.
+        assertEquals(1, hidlModel.phrases.size());
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Phrase hidlPhrase =
+                hidlModel.phrases.get(0);
+        assertEquals(123, hidlPhrase.id);
+        assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+        assertEquals("locale", hidlPhrase.locale);
+        assertEquals("text", hidlPhrase.text);
+        assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+                        | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+                hidlPhrase.recognitionModes);
+
+        return handle;
+    }
+
+    private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+            return loadPhraseModel_2_1(module, hwHandle);
+        } else {
+            return loadPhraseModel_2_0(module, hwHandle);
+        }
+    }
+
+    private void unloadModel(ISoundTriggerModule module, int handle, int hwHandle)
+            throws RemoteException {
+        module.unloadModel(handle);
+        verify(mHalDriver).unloadSoundModel(hwHandle);
+        verify(mAudioSessionProvider).releaseSession(101);
+    }
+
+    private SoundTriggerHwCallback 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);
+
+        RecognitionConfig config = createRecognitionConfig();
+
+        module.startRecognition(handle, config);
+        verify(mHalDriver).startRecognition(eq(hwHandle), any(), any(), anyInt());
+
+        android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig halConfig =
+                configCaptor.getValue();
+        assertTrue(halConfig.captureRequested);
+        assertEquals(102, halConfig.captureHandle);
+        assertEquals(103, halConfig.captureDevice);
+        assertEquals(1, halConfig.phrases.size());
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+                halConfig.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}, halConfig.data.toArray());
+
+        return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+    }
+
+    private SoundTriggerHwCallback 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;
+
+        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);
+
+        RecognitionConfig config = createRecognitionConfig();
+
+        module.startRecognition(handle, config);
+        verify(driver).startRecognition_2_1(eq(hwHandle), any(), any(), anyInt());
+
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig =
+                configCaptor.getValue();
+        assertTrue(halConfig.header.captureRequested);
+        assertEquals(102, halConfig.header.captureHandle);
+        assertEquals(103, halConfig.header.captureDevice);
+        assertEquals(1, halConfig.header.phrases.size());
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+                halConfig.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.data));
+
+        return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+    }
+
+    private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+            int hwHandle) throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+            return startRecognition_2_1(module, handle, hwHandle);
+        } else {
+            return startRecognition_2_0(module, handle, hwHandle);
+        }
+    }
+
+    private RecognitionConfig createRecognitionConfig() {
+        RecognitionConfig config = new RecognitionConfig();
+        config.captureRequested = true;
+        config.phraseRecognitionExtras = new PhraseRecognitionExtra[]{new PhraseRecognitionExtra()};
+        config.phraseRecognitionExtras[0].id = 123;
+        config.phraseRecognitionExtras[0].confidenceLevel = 4;
+        config.phraseRecognitionExtras[0].recognitionModes = 5;
+        config.phraseRecognitionExtras[0].levels = new ConfidenceLevel[]{new ConfidenceLevel()};
+        config.phraseRecognitionExtras[0].levels[0].userId = 234;
+        config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
+        config.data = new byte[]{5, 4, 3, 2, 1};
+        return config;
+    }
+
+    private void stopRecognition(ISoundTriggerModule module, int handle, int hwHandle)
+            throws RemoteException {
+        when(mHalDriver.stopRecognition(hwHandle)).thenReturn(0);
+        module.stopRecognition(handle);
+        verify(mHalDriver).stopRecognition(hwHandle);
+    }
+
+    private void verifyNotStartRecognition() throws RemoteException {
+        verify(mHalDriver, never()).startRecognition(anyInt(), any(), any(), anyInt());
+        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());
+        }
+    }
+
+
+    @Before
+    public void setUp() throws Exception {
+        clearInvocations(mHalDriver);
+        clearInvocations(mAudioSessionProvider);
+
+        // This binder is associated with the mock, so it can be cast to either version of the
+        // HAL interface.
+        final IHwBinder binder = new IHwBinder() {
+            @Override
+            public void transact(int code, HwParcel request, HwParcel reply, int flags)
+                    throws RemoteException {
+                // This is a little hacky, but a very easy way to gracefully reject a request for
+                // an unsupported interface (after queryLocalInterface() returns null, the client
+                // will attempt a remote transaction to obtain the interface. RemoteException will
+                // cause it to give up).
+                throw new RemoteException();
+            }
+
+            @Override
+            public IHwInterface queryLocalInterface(String descriptor) {
+                if (descriptor.equals("android.hardware.soundtrigger@2.0::ISoundTriggerHw")
+                        || descriptor.equals("android.hardware.soundtrigger@2.1::ISoundTriggerHw")
+                        && mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw
+                        || descriptor.equals("android.hardware.soundtrigger@2.2::ISoundTriggerHw")
+                        && mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw
+                        || descriptor.equals("android.hardware.soundtrigger@2.3::ISoundTriggerHw")
+                        && mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+                    return mHalDriver;
+                }
+                return null;
+            }
+
+            @Override
+            public boolean linkToDeath(DeathRecipient recipient, long cookie) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public boolean unlinkToDeath(DeathRecipient recipient) {
+                throw new UnsupportedOperationException();
+            }
+        };
+
+        when(mHalDriver.asBinder()).thenReturn(binder);
+    }
+
+    @Test
+    public void testSetUpAndTearDown() {
+    }
+
+    @Test
+    public void testListModules() throws Exception {
+        initService(true);
+        // Note: input and output properties are NOT the same type, even though they are in any way
+        // equivalent. One is a type that's exposed by the HAL and one is a type that's exposed by
+        // the service. The service actually performs a (trivial) conversion between the two.
+        SoundTriggerModuleDescriptor[] allDescriptors = mService.listModules();
+        assertEquals(1, allDescriptors.length);
+
+        SoundTriggerModuleProperties properties = allDescriptors[0].properties;
+
+        validateDefaultProperties(properties, true);
+    }
+
+    @Test
+    public void testAttachDetach() throws Exception {
+        // Normal attachment / detachment.
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        verify(callback).onRecognitionAvailabilityChange(true);
+        assertNotNull(module);
+        module.detach();
+    }
+
+    @Test
+    public void testAttachDetachNotAvailable() throws Exception {
+        // Attachment / detachment during external capture, with a module not supporting concurrent
+        // capture.
+        initService(false);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        verify(callback).onRecognitionAvailabilityChange(false);
+        assertNotNull(module);
+        module.detach();
+    }
+
+    @Test
+    public void testAttachDetachAvailable() throws Exception {
+        // Attachment / detachment during external capture, with a module supporting concurrent
+        // capture.
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        verify(callback).onRecognitionAvailabilityChange(true);
+        assertNotNull(module);
+        module.detach();
+    }
+
+    @Test
+    public void testLoadUnloadModel() throws Exception {
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        final int hwHandle = 7;
+        int handle = loadGenericModel(module, hwHandle);
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testLoadUnloadPhraseModel() throws Exception {
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        final int hwHandle = 73;
+        int handle = loadPhraseModel(module, hwHandle);
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testStartStopRecognition() throws Exception {
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        // Load the model.
+        final int hwHandle = 7;
+        int handle = loadGenericModel(module, hwHandle);
+
+        // Initiate a recognition.
+        startRecognition(module, handle, hwHandle);
+
+        // Stop the recognition.
+        stopRecognition(module, handle, hwHandle);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testStartStopPhraseRecognition() throws Exception {
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        // Load the model.
+        final int hwHandle = 67;
+        int handle = loadPhraseModel(module, hwHandle);
+
+        // Initiate a recognition.
+        startRecognition(module, handle, hwHandle);
+
+        // Stop the recognition.
+        stopRecognition(module, handle, hwHandle);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testRecognition() throws Exception {
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        // Load the model.
+        final int hwHandle = 7;
+        int handle = loadGenericModel(module, hwHandle);
+
+        // Initiate a recognition.
+        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+        // Signal a capture from the driver.
+        hwCallback.sendRecognitionEvent(hwHandle,
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEvent.class);
+        verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+        // Validate the event.
+        validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testPhraseRecognition() throws Exception {
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        // Load the model.
+        final int hwHandle = 7;
+        int handle = loadPhraseModel(module, hwHandle);
+
+        // Initiate a recognition.
+        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+        // Signal a capture from the driver.
+        hwCallback.sendPhraseRecognitionEvent(hwHandle,
+                android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEvent.class);
+        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+        // Validate the event.
+        validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testForceRecognition() throws Exception {
+        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+            return;
+        }
+
+        android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        // Load the model.
+        final int hwHandle = 17;
+        int handle = loadGenericModel(module, hwHandle);
+
+        // Initiate a recognition.
+        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+        // Force a trigger.
+        module.forceRecognitionEvent(handle);
+        verify(driver).getModelState(hwHandle);
+
+        // Signal a capture from the driver.
+        // '3' means 'forced', there's no constant for that in the HAL.
+        hwCallback.sendRecognitionEvent(hwHandle, 3);
+
+        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEvent.class);
+        verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+        // Validate the event.
+        validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+        // Stop the recognition.
+        stopRecognition(module, handle, hwHandle);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testForcePhraseRecognition() throws Exception {
+        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+            return;
+        }
+
+        android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+        initService(true);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+
+        // Load the model.
+        final int hwHandle = 17;
+        int handle = loadPhraseModel(module, hwHandle);
+
+        // Initiate a recognition.
+        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+        // Force a trigger.
+        module.forceRecognitionEvent(handle);
+        verify(driver).getModelState(hwHandle);
+
+        // Signal a capture from the driver.
+        // '3' means 'forced', there's no constant for that in the HAL.
+        hwCallback.sendPhraseRecognitionEvent(hwHandle, 3);
+
+        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEvent.class);
+        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+        // Validate the event.
+        validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+        // Stop the recognition.
+        stopRecognition(module, handle, hwHandle);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testAbortRecognition() throws Exception {
+        // Make sure the HAL doesn't support concurrent capture.
+        initService(false);
+        mService.setExternalCaptureState(false);
+
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        verify(callback).onRecognitionAvailabilityChange(true);
+
+        // Load the model.
+        final int hwHandle = 11;
+        int handle = loadGenericModel(module, hwHandle);
+
+        // Initiate a recognition.
+        startRecognition(module, handle, hwHandle);
+
+        // Abort.
+        mService.setExternalCaptureState(true);
+
+        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                RecognitionEvent.class);
+        verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+        // Validate the event.
+        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+
+        // Make sure we are notified of the lost availability.
+        verify(callback).onRecognitionAvailabilityChange(false);
+
+        // Attempt to start a new recognition - should get an abort event immediately, without
+        // involving the HAL.
+        clearInvocations(callback);
+        clearInvocations(mHalDriver);
+        module.startRecognition(handle, createRecognitionConfig());
+        verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+        verifyNotStartRecognition();
+
+        // Now enable it and make sure we are notified.
+        mService.setExternalCaptureState(false);
+        verify(callback).onRecognitionAvailabilityChange(true);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testAbortPhraseRecognition() throws Exception {
+        // Make sure the HAL doesn't support concurrent capture.
+        initService(false);
+        mService.setExternalCaptureState(false);
+
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        verify(callback).onRecognitionAvailabilityChange(true);
+
+        // Load the model.
+        final int hwHandle = 11;
+        int handle = loadPhraseModel(module, hwHandle);
+
+        // Initiate a recognition.
+        startRecognition(module, handle, hwHandle);
+
+        // Abort.
+        mService.setExternalCaptureState(true);
+
+        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+                PhraseRecognitionEvent.class);
+        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+        // Validate the event.
+        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+
+        // Make sure we are notified of the lost availability.
+        verify(callback).onRecognitionAvailabilityChange(false);
+
+        // Attempt to start a new recognition - should get an abort event immediately, without
+        // involving the HAL.
+        clearInvocations(callback);
+        clearInvocations(mHalDriver);
+        module.startRecognition(handle, createRecognitionConfig());
+        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+        verifyNotStartRecognition();
+
+        // Now enable it and make sure we are notified.
+        mService.setExternalCaptureState(false);
+        verify(callback).onRecognitionAvailabilityChange(true);
+
+        // Unload the model.
+        unloadModel(module, handle, hwHandle);
+        module.detach();
+    }
+
+    @Test
+    public void testNotAbortRecognitionConcurrent() throws Exception {
+        // Make sure the HAL supports concurrent capture.
+        initService(true);
+
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        verify(callback).onRecognitionAvailabilityChange(true);
+        clearInvocations(callback);
+
+        // Load the model.
+        final int hwHandle = 13;
+        int handle = loadGenericModel(module, hwHandle);
+
+        // Initiate a recognition.
+        startRecognition(module, handle, hwHandle);
+
+        // Signal concurrent capture. Shouldn't abort.
+        mService.setExternalCaptureState(true);
+        verify(callback, never()).onRecognition(anyInt(), any());
+        verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+        // Stop the recognition.
+        stopRecognition(module, handle, hwHandle);
+
+        // Initiating a new one should work fine.
+        clearInvocations(mHalDriver);
+        startRecognition(module, handle, hwHandle);
+        verify(callback, never()).onRecognition(anyInt(), any());
+        stopRecognition(module, handle, hwHandle);
+
+        // Unload the model.
+        module.unloadModel(handle);
+        module.detach();
+    }
+
+    @Test
+    public void testNotAbortPhraseRecognitionConcurrent() throws Exception {
+        // Make sure the HAL supports concurrent capture.
+        initService(true);
+
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        verify(callback).onRecognitionAvailabilityChange(true);
+        clearInvocations(callback);
+
+        // Load the model.
+        final int hwHandle = 13;
+        int handle = loadPhraseModel(module, hwHandle);
+
+        // Initiate a recognition.
+        startRecognition(module, handle, hwHandle);
+
+        // Signal concurrent capture. Shouldn't abort.
+        mService.setExternalCaptureState(true);
+        verify(callback, never()).onPhraseRecognition(anyInt(), any());
+        verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+        // Stop the recognition.
+        stopRecognition(module, handle, hwHandle);
+
+        // Initiating a new one should work fine.
+        clearInvocations(mHalDriver);
+        startRecognition(module, handle, hwHandle);
+        verify(callback, never()).onRecognition(anyInt(), any());
+        stopRecognition(module, handle, hwHandle);
+
+        // Unload the model.
+        module.unloadModel(handle);
+        module.detach();
+    }
+
+    @Test
+    public void testParameterSupported() throws Exception {
+        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+            return;
+        }
+
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        initService(false);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        final int hwHandle = 12;
+        int modelHandle = loadGenericModel(module, hwHandle);
+
+        doAnswer((Answer<Void>) invocation -> {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+                    resultCallback = invocation.getArgument(2);
+            android.hardware.soundtrigger.V2_3.ModelParameterRange range =
+                    new android.hardware.soundtrigger.V2_3.ModelParameterRange();
+            range.start = 23;
+            range.end = 45;
+            OptionalModelParameterRange optionalRange = new OptionalModelParameterRange();
+            optionalRange.range(range);
+            resultCallback.onValues(0, optionalRange);
+            return null;
+        }).when(driver).queryParameter(eq(hwHandle),
+                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+        ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+                ModelParameter.THRESHOLD_FACTOR);
+
+        verify(driver).queryParameter(eq(hwHandle),
+                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+        assertEquals(23, range.minInclusive);
+        assertEquals(45, range.maxInclusive);
+    }
+
+    @Test
+    public void testParameterNotSupportedOld() throws Exception {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            return;
+        }
+
+        initService(false);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        final int hwHandle = 13;
+        int modelHandle = loadGenericModel(module, hwHandle);
+
+        ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+                ModelParameter.THRESHOLD_FACTOR);
+
+        assertNull(range);
+    }
+
+    @Test
+    public void testParameterNotSupported() throws Exception {
+        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+            return;
+        }
+
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        initService(false);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        final int hwHandle = 13;
+        int modelHandle = loadGenericModel(module, hwHandle);
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+                    resultCallback = invocation.getArgument(2);
+            // This is the return of this method.
+            resultCallback.onValues(0, new OptionalModelParameterRange());
+            return null;
+        }).when(driver).queryParameter(eq(hwHandle),
+                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+        ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+                ModelParameter.THRESHOLD_FACTOR);
+
+        verify(driver).queryParameter(eq(hwHandle),
+                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+        assertNull(range);
+    }
+
+    @Test
+    public void testGetParameter() throws Exception {
+        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+            return;
+        }
+
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        initService(false);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        final int hwHandle = 14;
+        int modelHandle = loadGenericModel(module, hwHandle);
+
+        doAnswer(invocation -> {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
+                    resultCallback = invocation.getArgument(2);
+            // This is the return of this method.
+            resultCallback.onValues(0, 234);
+            return null;
+        }).when(driver).getParameter(eq(hwHandle),
+                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+        int value = module.getModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR);
+
+        verify(driver).getParameter(eq(hwHandle),
+                eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+        assertEquals(234, value);
+    }
+
+    @Test
+    public void testSetParameter() throws Exception {
+        if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+            return;
+        }
+
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        initService(false);
+        ISoundTriggerCallback callback = createCallbackMock();
+        ISoundTriggerModule module = mService.attach(0, callback);
+        final int hwHandle = 17;
+        int modelHandle = loadGenericModel(module, hwHandle);
+
+        when(driver.setParameter(hwHandle,
+                android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
+                456)).thenReturn(0);
+
+        module.setModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR, 456);
+
+        verify(driver).setParameter(hwHandle,
+                android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR, 456);
+    }
+
+    private static class SoundTriggerHwCallback {
+        private final android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback mCallback;
+        private final int mCookie;
+
+        SoundTriggerHwCallback(android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback,
+                int cookie) {
+            mCallback = callback;
+            mCookie = cookie;
+        }
+
+        private void sendRecognitionEvent(int hwHandle, int status) throws RemoteException {
+            if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+                ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).recognitionCallback_2_1(
+                        createRecognitionEvent_2_1(hwHandle, status), mCookie);
+            } else {
+                mCallback.recognitionCallback(createRecognitionEvent_2_0(hwHandle, status),
+                        mCookie);
+            }
+        }
+
+        private void sendPhraseRecognitionEvent(int hwHandle, int status) throws RemoteException {
+            if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+                ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).phraseRecognitionCallback_2_1(
+                        createPhraseRecognitionEvent_2_1(hwHandle, status), mCookie);
+            } else {
+                mCallback.phraseRecognitionCallback(
+                        createPhraseRecognitionEvent_2_0(hwHandle, status), mCookie);
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
index cb49fef..2429cfc 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
@@ -453,7 +453,7 @@
 
         @Override
         public boolean isDeviceTimeZoneInitialized() {
-            return mTimeZoneId != null;
+            return mTimeZoneId.getLatest() != null;
         }
 
         @Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 0b4760d..a328568 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.spy;
 
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -271,6 +272,18 @@
     }
 
     @Test
+    public void testRankingScoreOverrides() {
+        NotificationComparator comp = new NotificationComparator(mContext);
+        NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
+        assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
+
+        when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f);
+        assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
+        assertTrue(comp.compare(mRecordCheater, recordMinCallNonInterruptive) > 0);
+        assertTrue(comp.compare(mRecordColorizedCall, recordMinCallNonInterruptive) < 0);
+    }
+
+    @Test
     public void testMessaging() {
         NotificationComparator comp = new NotificationComparator(mContext);
         assertTrue(comp.isImportantMessaging(mRecordInlineReply));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 72baedb..8ade4d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -61,6 +61,7 @@
 
 import android.graphics.Insets;
 import android.graphics.Matrix;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.Size;
@@ -222,8 +223,7 @@
         final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
         final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, "imeWindow");
 
-        // Setting FLAG_NOT_FOCUSABLE without FLAG_ALT_FOCUSABLE_IM prevents the window from being
-        // an IME target.
+        // Setting FLAG_NOT_FOCUSABLE prevents the window from being an IME target.
         appWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
         imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
 
@@ -231,7 +231,7 @@
         appWindow.setHasSurface(true);
         imeWindow.setHasSurface(true);
 
-        // Windows without flags (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM) can't be IME targets
+        // Windows with FLAG_NOT_FOCUSABLE can't be IME targets
         assertFalse(appWindow.canBeImeTarget());
         assertFalse(imeWindow.canBeImeTarget());
 
@@ -239,11 +239,22 @@
         appWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
         imeWindow.mAttrs.flags |= (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
 
-        // Visible app window with flags can be IME target while an IME window can never be an IME
-        // target regardless of its visibility or flags.
-        assertTrue(appWindow.canBeImeTarget());
+        // Visible app window with flags FLAG_NOT_FOCUSABLE or FLAG_ALT_FOCUSABLE_IM can't be IME
+        // target while an IME window can never be an IME target regardless of its visibility
+        // or flags.
+        assertFalse(appWindow.canBeImeTarget());
         assertFalse(imeWindow.canBeImeTarget());
 
+        // b/145812508: special legacy use-case for transparent/translucent windows.
+        appWindow.mAttrs.format = PixelFormat.TRANSPARENT;
+        assertTrue(appWindow.canBeImeTarget());
+
+        appWindow.mAttrs.format = PixelFormat.OPAQUE;
+        appWindow.mAttrs.flags &= ~FLAG_ALT_FOCUSABLE_IM;
+        assertFalse(appWindow.canBeImeTarget());
+        appWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
+        assertTrue(appWindow.canBeImeTarget());
+
         // Make windows invisible
         appWindow.hideLw(false /* doAnimation */);
         imeWindow.hideLw(false /* doAnimation */);
diff --git a/services/usb/java/com/android/server/usb/UsbHandlerManager.java b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
index 1730d8f..f311274 100644
--- a/services/usb/java/com/android/server/usb/UsbHandlerManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
@@ -59,8 +60,9 @@
         if (uri != null && uri.length() > 0) {
             // display URI to user
             Intent dialogIntent = createDialogIntent();
-            dialogIntent.setClassName("com.android.systemui",
-                    "com.android.systemui.usb.UsbAccessoryUriActivity");
+            dialogIntent.setComponent(ComponentName.unflattenFromString(
+                    mContext.getResources().getString(
+                            com.android.internal.R.string.config_usbAccessoryUriActivity)));
             dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
             dialogIntent.putExtra("uri", uri);
             try {
@@ -84,8 +86,9 @@
             @Nullable UsbAccessory accessory) {
         Intent resolverIntent = createDialogIntent();
         // start UsbConfirmActivity if there is only one choice
-        resolverIntent.setClassName("com.android.systemui",
-                "com.android.systemui.usb.UsbConfirmActivity");
+        resolverIntent.setComponent(ComponentName.unflattenFromString(
+                mContext.getResources().getString(
+                        com.android.internal.R.string.config_usbConfirmActivity)));
         resolverIntent.putExtra("rinfo", rInfo);
         UserHandle user =
                 UserHandle.getUserHandleForUid(rInfo.activityInfo.applicationInfo.uid);
@@ -115,8 +118,9 @@
     void selectUsbHandler(@NonNull ArrayList<ResolveInfo> matches,
             @NonNull UserHandle user, @NonNull Intent intent) {
         Intent resolverIntent = createDialogIntent();
-        resolverIntent.setClassName("com.android.systemui",
-                "com.android.systemui.usb.UsbResolverActivity");
+        resolverIntent.setComponent(ComponentName.unflattenFromString(
+                mContext.getResources().getString(
+                        com.android.internal.R.string.config_usbResolverActivity)));
         resolverIntent.putParcelableArrayListExtra("rlist", matches);
         resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
 
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
index 749258e..c3e2013 100644
--- a/services/usb/java/com/android/server/usb/UsbPortManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -34,6 +34,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -225,8 +226,8 @@
 
             Intent intent = new Intent();
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            intent.setClassName("com.android.systemui",
-                    "com.android.systemui.usb.UsbContaminantActivity");
+            intent.setComponent(ComponentName.unflattenFromString(r.getString(
+                    com.android.internal.R.string.config_usbContaminantActivity)));
             intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort));
 
             PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java
index 86016bb..095e8e9 100644
--- a/services/usb/java/com/android/server/usb/UsbSerialReader.java
+++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java
@@ -75,12 +75,14 @@
         if (uid != Process.SYSTEM_UID) {
             enforcePackageBelongsToUid(uid, packageName);
 
+            UserHandle user = Binder.getCallingUserHandle();
             int packageTargetSdkVersion;
             long token = Binder.clearCallingIdentity();
             try {
                 PackageInfo pkg;
                 try {
-                    pkg = mContext.getPackageManager().getPackageInfo(packageName, 0);
+                    pkg = mContext.getPackageManager()
+                            .getPackageInfoAsUser(packageName, 0, user.getIdentifier());
                 } catch (PackageManager.NameNotFoundException e) {
                     throw new RemoteException("package " + packageName + " cannot be found");
                 }
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 58f5484..2a94393 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -515,8 +516,8 @@
         intent.putExtra(Intent.EXTRA_UID, uid);
         intent.putExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, canBeDefault);
         intent.putExtra(UsbManager.EXTRA_PACKAGE, packageName);
-        intent.setClassName("com.android.systemui",
-                "com.android.systemui.usb.UsbPermissionActivity");
+        intent.setComponent(ComponentName.unflattenFromString(userContext.getResources().getString(
+                com.android.internal.R.string.config_usbPermissionActivity)));
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
         try {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index c063279..0becaf2 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -695,6 +695,15 @@
     public static final String EVENT_CALL_HOLD_FAILED = "android.telecom.event.CALL_HOLD_FAILED";
 
     /**
+     * Connection event used to inform Telecom when a switch operation on a call has failed.
+     * <p>
+     * Sent via {@link #sendConnectionEvent(String, Bundle)}.  The {@link Bundle} parameter is
+     * expected to be null when this connection event is used.
+     */
+    public static final String EVENT_CALL_SWITCH_FAILED =
+            "android.telecom.event.CALL_SWITCH_FAILED";
+
+    /**
      * Connection event used to inform {@link InCallService}s when the process of merging a
      * Connection into a conference has begun.
      * <p>
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 001888c..7381acb 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -419,12 +419,33 @@
             KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
 
     /**
-     * Override the device's configuration for the ImsService to use for this SIM card.
+     * The package name containing the ImsService that will be bound to the telephony framework to
+     * support both IMS MMTEL and RCS feature functionality instead of the device default
+     * ImsService for this subscription.
+     * @deprecated Use {@link #KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING} and
+     * {@link #KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING} instead to configure these values
+     * separately. If any of those values are not empty, they will override this value.
      */
     public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING =
             "config_ims_package_override_string";
 
     /**
+     * The package name containing the ImsService that will be bound to the telephony framework to
+     * support IMS MMTEL feature functionality instead of the device default ImsService for this
+     * subscription.
+     */
+    public static final String KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING =
+            "config_ims_mmtel_package_override_string";
+
+    /**
+     * The package name containing the ImsService that will be bound to the telephony framework to
+     * support IMS RCS feature functionality instead of the device default ImsService for this
+     * subscription.
+     */
+    public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING =
+            "config_ims_rcs_package_override_string";
+
+    /**
      * Override the package that will manage {@link SubscriptionPlan}
      * information instead of the {@link CarrierService} that defines this
      * value.
@@ -1946,6 +1967,12 @@
             "allow_add_call_during_video_call";
 
     /**
+     * When false, indicates that holding a video call is disabled
+     */
+    public static final String KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL =
+            "allow_holding_video_call";
+
+    /**
      * When true, indicates that the HD audio icon in the in-call screen should not be shown for
      * VoWifi calls.
      * @hide
@@ -2176,7 +2203,7 @@
      * the start of the next month.
      * <p>
      * This setting may be still overridden by explicit user choice. By default,
-     * the platform value will be used.
+     * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
      */
     public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT =
             "monthly_data_cycle_day_int";
@@ -2185,10 +2212,7 @@
      * When {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG},
      * or {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} are set to this value, the platform default
      * value will be used for that key.
-     *
-     * @hide
      */
-    @Deprecated
     public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1;
 
     /**
@@ -2212,8 +2236,8 @@
      * If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data usage warning will
      * be disabled.
      * <p>
-     * This setting may be overridden by explicit user choice. By default, the platform value
-     * will be used.
+     * This setting may be overridden by explicit user choice. By default,
+     * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
      */
     public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG =
             "data_warning_threshold_bytes_long";
@@ -2221,8 +2245,7 @@
     /**
      * Controls if the device should automatically notify the user as they reach
      * their cellular data warning. When set to {@code false} the carrier is
-     * expected to have implemented their own notification mechanism.
-     * @hide
+     * expected to have implemented their own notification mechanism. {@code true} by default.
      */
     public static final String KEY_DATA_WARNING_NOTIFICATION_BOOL =
             "data_warning_notification_bool";
@@ -2244,8 +2267,8 @@
      * phone. If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data limit will be
      * disabled.
      * <p>
-     * This setting may be overridden by explicit user choice. By default, the platform value
-     * will be used.
+     * This setting may be overridden by explicit user choice. By default,
+     * {@link #DATA_CYCLE_USE_PLATFORM_DEFAULT} will be used.
      */
     public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG =
             "data_limit_threshold_bytes_long";
@@ -2253,8 +2276,7 @@
     /**
      * Controls if the device should automatically notify the user as they reach
      * their cellular data limit. When set to {@code false} the carrier is
-     * expected to have implemented their own notification mechanism.
-     * @hide
+     * expected to have implemented their own notification mechanism. {@code true} by default.
      */
     public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL =
             "data_limit_notification_bool";
@@ -2262,8 +2284,7 @@
     /**
      * Controls if the device should automatically notify the user when rapid
      * cellular data usage is observed. When set to {@code false} the carrier is
-     * expected to have implemented their own notification mechanism.
-     * @hide
+     * expected to have implemented their own notification mechanism.  {@code true} by default.
      */
     public static final String KEY_DATA_RAPID_NOTIFICATION_BOOL =
             "data_rapid_notification_bool";
@@ -3486,6 +3507,8 @@
         sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
+        sDefaults.putString(KEY_CONFIG_IMS_MMTEL_PACKAGE_OVERRIDE_STRING, null);
+        sDefaults.putString(KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING, null);
         sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
@@ -3629,6 +3652,7 @@
         sDefaults.putBoolean(KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL, false);
         sDefaults.putBoolean(KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL, true);
         sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true);
+        sDefaults.putBoolean(KEY_ALLOW_HOLDING_VIDEO_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true);
         sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true);
         sDefaults.putBoolean(KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO, false);
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 02d8be3..39af34c 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -16,7 +16,10 @@
 
 package android.telephony.ims;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.telephony.SubscriptionManager;
 
@@ -25,12 +28,15 @@
  *
  * @hide
  */
+@SystemApi
+@TestApi
 @SystemService(Context.TELEPHONY_IMS_SERVICE)
 public class ImsManager {
 
     private Context mContext;
 
-    public ImsManager(Context context) {
+    /** @hide */
+    public ImsManager(@NonNull Context context) {
         mContext = context;
     }
 
@@ -41,6 +47,7 @@
      * @throws IllegalArgumentException if the subscription is invalid.
      * @return a ImsRcsManager instance with the specific subscription ID.
      */
+    @NonNull
     public ImsRcsManager getImsRcsManager(int subscriptionId) {
         if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
             throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId);
@@ -56,6 +63,7 @@
      * @throws IllegalArgumentException if the subscription is invalid.
      * @return a ImsMmTelManager instance with the specific subscription ID.
      */
+    @NonNull
     public ImsMmTelManager getImsMmTelManager(int subscriptionId) {
         if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) {
             throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId);
diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java
index 16fbae2..aebe780 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -205,10 +205,10 @@
     }
 
     /**
+     * Indicate if the ModemActivityInfo is invalid due to modem's invalid reporting.
+     *
      * @return {@code true} if this {@link ModemActivityInfo} record is valid,
      * {@code false} otherwise.
-     *
-     * @hide
      */
     public boolean isValid() {
         for (TransmitPower powerInfo : getTransmitPowerInfo()) {
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index 0ceb103..c8b8ffb 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -222,9 +222,13 @@
     private NetworkScanRequest(Parcel in) {
         mScanType = in.readInt();
         Parcelable[] tempSpecifiers = in.readParcelableArray(Object.class.getClassLoader());
-        mSpecifiers = new RadioAccessSpecifier[tempSpecifiers.length];
-        for (int i = 0; i < tempSpecifiers.length; i++) {
-            mSpecifiers[i] = (RadioAccessSpecifier) tempSpecifiers[i];
+        if (tempSpecifiers != null) {
+            mSpecifiers = new RadioAccessSpecifier[tempSpecifiers.length];
+            for (int i = 0; i < tempSpecifiers.length; i++) {
+                mSpecifiers[i] = (RadioAccessSpecifier) tempSpecifiers[i];
+            }
+        } else {
+            mSpecifiers = null;
         }
         mSearchPeriodicity = in.readInt();
         mMaxSearchTime = in.readInt();
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 257d634..78ad5c5 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -19,8 +19,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.net.LinkProperties;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Annotation.ApnType;
@@ -29,6 +31,8 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.data.ApnSetting;
 
+import dalvik.system.VMRuntime;
+
 import java.util.Objects;
 
 
@@ -46,35 +50,62 @@
  *   <li>Data connection fail cause.
  * </ul>
  *
- * @hide
  */
-@SystemApi
 public final class PreciseDataConnectionState implements Parcelable {
 
     private @DataState int mState = TelephonyManager.DATA_UNKNOWN;
     private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
     private @DataFailureCause int mFailCause = DataFailCause.NONE;
-    private @ApnType int mAPNTypes = ApnSetting.TYPE_NONE;
-    private String mAPN = "";
+    private @ApnType int mApnTypes = ApnSetting.TYPE_NONE;
+    private String mApn = "";
     private LinkProperties mLinkProperties = null;
+    private ApnSetting mApnSetting = null;
 
     /**
      * Constructor
      *
+     * @deprecated this constructor has been superseded and should not be used.
      * @hide
      */
-    @UnsupportedAppUsage
+    @TestApi
+    @Deprecated
+    @UnsupportedAppUsage // (maxTargetSdk = Build.VERSION_CODES.Q)
+    // FIXME: figure out how to remove the UnsupportedAppUsage and delete this constructor
     public PreciseDataConnectionState(@DataState int state,
                                       @NetworkType int networkType,
-                                      @ApnType int apnTypes, String apn,
-                                      LinkProperties linkProperties,
+                                      @ApnType int apnTypes, @NonNull String apn,
+                                      @Nullable LinkProperties linkProperties,
                                       @DataFailureCause int failCause) {
+        this(state, networkType, apnTypes, apn, linkProperties, failCause, null);
+    }
+
+
+    /**
+     * Constructor
+     *
+     * @param state the state of the data connection
+     * @param networkType the access network that is/would carry this data connection
+     * @param apnTypes the APN types that this data connection carries
+     * @param apnSetting if there is a valid APN for this Data Connection, then the APN Settings;
+     *        if there is no valid APN setting for the specific type, then this will be null
+     * @param linkProperties if the data connection is connected, the properties of the connection
+     * @param failCause in case a procedure related to this data connection fails, a non-zero error
+     *        code indicating the cause of the failure.
+     * @hide
+     */
+    public PreciseDataConnectionState(@DataState int state,
+                                      @NetworkType int networkType,
+                                      @ApnType int apnTypes, @NonNull String apn,
+                                      @Nullable LinkProperties linkProperties,
+                                      @DataFailureCause int failCause,
+                                      @Nullable ApnSetting apnSetting) {
         mState = state;
         mNetworkType = networkType;
-        mAPNTypes = apnTypes;
-        mAPN = apn;
+        mApnTypes = apnTypes;
+        mApn = apn;
         mLinkProperties = linkProperties;
         mFailCause = failCause;
+        mApnSetting = apnSetting;
     }
 
     /**
@@ -93,76 +124,160 @@
     private PreciseDataConnectionState(Parcel in) {
         mState = in.readInt();
         mNetworkType = in.readInt();
-        mAPNTypes = in.readInt();
-        mAPN = in.readString();
-        mLinkProperties = (LinkProperties)in.readParcelable(null);
+        mApnTypes = in.readInt();
+        mApn = in.readString();
+        mLinkProperties = (LinkProperties) in.readParcelable(null);
         mFailCause = in.readInt();
+        mApnSetting = (ApnSetting) in.readParcelable(null);
     }
 
     /**
      * Returns the state of data connection that supported the apn types returned by
      * {@link #getDataConnectionApnTypeBitMask()}
+     *
+     * @deprecated use {@link #getState()}
+     * @hide
      */
+    @Deprecated
+    @SystemApi
     public @DataState int getDataConnectionState() {
+        if (mState == TelephonyManager.DATA_DISCONNECTING
+                && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+            return TelephonyManager.DATA_CONNECTED;
+        }
+
+        return mState;
+    }
+
+    /**
+     * Returns the high-level state of this data connection.
+     */
+    public @DataState int getState() {
         return mState;
     }
 
     /**
      * Returns the network type associated with this data connection.
+     *
+     * @deprecated use {@link getNetworkType()}
      * @hide
      */
+    @Deprecated
+    @SystemApi
     public @NetworkType int getDataConnectionNetworkType() {
         return mNetworkType;
     }
 
     /**
-     * Returns the data connection APN types supported by this connection and triggers
-     * {@link PreciseDataConnectionState} change.
+     * Returns the network type associated with this data connection.
+     *
+     * Return the current/latest (radio) bearer technology that carries this data connection.
+     * For a variety of reasons, the network type can change during the life of the data
+     * connection, and this information is not reliable unless the physical link is currently
+     * active; (there is currently no mechanism to know whether the physical link is active at
+     * any given moment). Thus, this value is generally correct but may not be relied-upon to
+     * represent the status of the radio bearer at any given moment.
      */
-    public @ApnType int getDataConnectionApnTypeBitMask() {
-        return mAPNTypes;
+    public @NetworkType int getNetworkType() {
+        return mNetworkType;
     }
 
     /**
-     * Returns APN {@link ApnSetting} of this data connection.
+     * Returns the APN types mapped to this data connection.
+     *
+     * @deprecated use {@link #getApnSetting()}
+     * @hide
      */
-    @Nullable
+    @Deprecated
+    @SystemApi
+    public @ApnType int getDataConnectionApnTypeBitMask() {
+        return mApnTypes;
+    }
+
+    /**
+     * Returns APN of this data connection.
+     *
+     * @deprecated use {@link #getApnSetting()}
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @Deprecated
     public String getDataConnectionApn() {
-        return mAPN;
+        return mApn;
     }
 
     /**
      * Get the properties of the network link {@link LinkProperties}.
+     *
+     * @deprecated use {@link #getLinkProperties()}
      * @hide
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @SystemApi
+    @Nullable
     public LinkProperties getDataConnectionLinkProperties() {
         return mLinkProperties;
     }
 
     /**
-     * Returns data connection fail cause, in case there was a failure.
+     * Get the properties of the network link {@link LinkProperties}.
      */
-    public @Annotation.DataFailureCause int getDataConnectionFailCause() {
+    @Nullable
+    public LinkProperties getLinkProperties() {
+        return mLinkProperties;
+    }
+
+    /**
+     * Returns the cause code generated by the most recent state change.
+     *
+     * @deprecated use {@link #getLastCauseCode()}
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    public int getDataConnectionFailCause() {
         return mFailCause;
     }
 
+    /**
+     * Returns the cause code generated by the most recent state change.
+     *
+     * Return the cause code for the most recent change in {@link #getState}. In the event of an
+     * error, this cause code will be non-zero.
+     */
+    // FIXME(b144774287): some of these cause codes should have a prescribed meaning.
+    public int getLastCauseCode() {
+        return mFailCause;
+    }
+
+    /**
+     * Return the APN Settings for this data connection.
+     *
+     * Returns the ApnSetting that was used to configure this data connection.
+     */
+    // FIXME: This shouldn't be nullable; update once the ApnSetting is supplied correctly
+    @Nullable ApnSetting getApnSetting() {
+        return mApnSetting;
+    }
+
     @Override
     public int describeContents() {
         return 0;
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeInt(mState);
         out.writeInt(mNetworkType);
-        out.writeInt(mAPNTypes);
-        out.writeString(mAPN);
+        out.writeInt(mApnTypes);
+        out.writeString(mApn);
         out.writeParcelable(mLinkProperties, flags);
         out.writeInt(mFailCause);
+        out.writeParcelable(mApnSetting, flags);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
+    public static final @NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
             = new Parcelable.Creator<PreciseDataConnectionState>() {
 
         public PreciseDataConnectionState createFromParcel(Parcel in) {
@@ -176,8 +291,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mState, mNetworkType, mAPNTypes, mAPN, mLinkProperties,
-                mFailCause);
+        return Objects.hash(mState, mNetworkType, mApnTypes, mApn, mLinkProperties,
+                mFailCause, mApnSetting);
     }
 
     @Override
@@ -188,11 +303,12 @@
         }
 
         PreciseDataConnectionState other = (PreciseDataConnectionState) obj;
-        return Objects.equals(mAPN, other.mAPN) && mAPNTypes == other.mAPNTypes
+        return Objects.equals(mApn, other.mApn) && mApnTypes == other.mApnTypes
                 && mFailCause == other.mFailCause
                 && Objects.equals(mLinkProperties, other.mLinkProperties)
                 && mNetworkType == other.mNetworkType
-                && mState == other.mState;
+                && mState == other.mState
+                && Objects.equals(mApnSetting, other.mApnSetting);
     }
 
     @NonNull
@@ -202,10 +318,11 @@
 
         sb.append("Data Connection state: " + mState);
         sb.append(", Network type: " + mNetworkType);
-        sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mAPNTypes));
-        sb.append(", APN: " + mAPN);
+        sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mApnTypes));
+        sb.append(", APN: " + mApn);
         sb.append(", Link properties: " + mLinkProperties);
         sb.append(", Fail cause: " + DataFailCause.toString(mFailCause));
+        sb.append(", Apn Setting: " + mApnSetting);
 
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index d2c8517..d7d85c2 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -941,7 +941,7 @@
                 rtString = "LTE_CA";
                 break;
             case RIL_RADIO_TECHNOLOGY_NR:
-                rtString = "LTE_NR";
+                rtString = "NR_SA";
                 break;
             default:
                 rtString = "Unexpected";
@@ -1479,8 +1479,9 @@
                 return AccessNetworkType.CDMA2000;
             case RIL_RADIO_TECHNOLOGY_LTE:
             case RIL_RADIO_TECHNOLOGY_LTE_CA:
-            case RIL_RADIO_TECHNOLOGY_NR:
                 return AccessNetworkType.EUTRAN;
+            case RIL_RADIO_TECHNOLOGY_NR:
+                return AccessNetworkType.NGRAN;
             case RIL_RADIO_TECHNOLOGY_IWLAN:
                 return AccessNetworkType.IWLAN;
             case RIL_RADIO_TECHNOLOGY_UNKNOWN:
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 37f3388..cab5286 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1998,6 +1998,27 @@
         }
     }
 
+    /**
+     * Gets the total capacity of SMS storage on RUIM and SIM cards
+     *
+     * @return the total capacity count of SMS on RUIM and SIM cards
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getSmsCapacityOnIcc() {
+        int ret = 0;
+        try {
+            ISms iccISms = getISmsService();
+            if (iccISms != null) {
+                ret = iccISms.getSmsCapacityOnIccForSubscriber(getSubscriptionId());
+            }
+        } catch (RemoteException ex) {
+            //ignore it
+        }
+        return ret;
+    }
+
     // see SmsMessage.getStatusOnIcc
 
     /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5a4a3d0..78c5e43 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -38,6 +38,9 @@
 import android.annotation.WorkerThread;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -142,6 +145,14 @@
     private static final String TAG = "TelephonyManager";
 
     /**
+     * To expand the error codes for {@link TelephonyManager#updateAvailableNetworks} and
+     * {@link TelephonyManager#setPreferredOpportunisticDataSubscription}.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
+
+    /**
      * The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)}
      * into the ResultReceiver Bundle.
      * @hide
@@ -790,18 +801,6 @@
     public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY;
 
     /**
-     * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
-     * for an String representation of the data interface.
-     *
-     * <p class="note">
-     * Retrieve with
-     * {@link android.content.Intent#getParcelableExtra(String name)}.
-     *
-     * @hide
-     */
-    public static final String EXTRA_DATA_LINK_PROPERTIES_KEY = PhoneConstants.DATA_LINK_PROPERTIES_KEY;
-
-    /**
      * Broadcast intent action for letting the default dialer to know to show voicemail
      * notification.
      *
@@ -2864,6 +2863,24 @@
     //
     //
 
+    /** @hide */
+    @IntDef(prefix = {"SIM_STATE_"},
+            value = {
+                    SIM_STATE_UNKNOWN,
+                    SIM_STATE_ABSENT,
+                    SIM_STATE_PIN_REQUIRED,
+                    SIM_STATE_PUK_REQUIRED,
+                    SIM_STATE_NETWORK_LOCKED,
+                    SIM_STATE_READY,
+                    SIM_STATE_NOT_READY,
+                    SIM_STATE_PERM_DISABLED,
+                    SIM_STATE_CARD_IO_ERROR,
+                    SIM_STATE_CARD_RESTRICTED,
+                    SIM_STATE_LOADED,
+                    SIM_STATE_PRESENT,
+            })
+    public @interface SimState {}
+
     /**
      * SIM card state: Unknown. Signifies that the SIM is in transition
      * between states. For example, when the user inputs the SIM pin
@@ -3069,7 +3086,7 @@
      * @see #SIM_STATE_CARD_IO_ERROR
      * @see #SIM_STATE_CARD_RESTRICTED
      */
-    public int getSimState() {
+    public @SimState int getSimState() {
         int simState = getSimStateIncludingLoaded();
         if (simState == SIM_STATE_LOADED) {
             simState = SIM_STATE_READY;
@@ -3077,7 +3094,7 @@
         return simState;
     }
 
-    private int getSimStateIncludingLoaded() {
+    private @SimState int getSimStateIncludingLoaded() {
         int slotIndex = getSlotIndex();
         // slotIndex may be invalid due to sim being absent. In that case query all slots to get
         // sim state
@@ -3111,7 +3128,7 @@
      * @hide
      */
     @SystemApi
-    public int getSimCardState() {
+    public @SimState int getSimCardState() {
         int simState = getSimState();
         return getSimCardStateFromSimState(simState);
     }
@@ -3131,7 +3148,7 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public int getSimCardState(int physicalSlotIndex) {
+    public @SimState int getSimCardState(int physicalSlotIndex) {
         int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex));
         return getSimCardStateFromSimState(simState);
     }
@@ -3141,7 +3158,7 @@
      * @param simState
      * @return SIM card state
      */
-    private int getSimCardStateFromSimState(int simState) {
+    private @SimState int getSimCardStateFromSimState(int simState) {
         switch (simState) {
             case SIM_STATE_UNKNOWN:
             case SIM_STATE_ABSENT:
@@ -3181,7 +3198,7 @@
      * @hide
      */
     @SystemApi
-    public int getSimApplicationState() {
+    public @SimState int getSimApplicationState() {
         int simState = getSimStateIncludingLoaded();
         return getSimApplicationStateFromSimState(simState);
     }
@@ -3204,7 +3221,7 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public int getSimApplicationState(int physicalSlotIndex) {
+    public @SimState int getSimApplicationState(int physicalSlotIndex) {
         int simState =
                 SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex));
         return getSimApplicationStateFromSimState(simState);
@@ -3215,7 +3232,7 @@
      * @param simState
      * @return SIM application state
      */
-    private int getSimApplicationStateFromSimState(int simState) {
+    private @SimState int getSimApplicationStateFromSimState(int simState) {
         switch (simState) {
             case SIM_STATE_UNKNOWN:
             case SIM_STATE_ABSENT:
@@ -3272,7 +3289,7 @@
      * @see #SIM_STATE_CARD_IO_ERROR
      * @see #SIM_STATE_CARD_RESTRICTED
      */
-    public int getSimState(int slotIndex) {
+    public @SimState int getSimState(int slotIndex) {
         int simState = SubscriptionManager.getSimStateForSlotIndex(slotIndex);
         if (simState == SIM_STATE_LOADED) {
             simState = SIM_STATE_READY;
@@ -5100,6 +5117,7 @@
             DATA_CONNECTING,
             DATA_CONNECTED,
             DATA_SUSPENDED,
+            DATA_DISCONNECTING,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DataState{}
@@ -5116,6 +5134,12 @@
      * traffic is temporarily unavailable. For example, in a 2G network,
      * data activity may be suspended when a voice call arrives. */
     public static final int DATA_SUSPENDED      = 3;
+    /**
+     * Data connection state: Disconnecting.
+     *
+     * IP traffic may be available but will cease working imminently.
+     */
+    public static final int DATA_DISCONNECTING = 4;
 
     /**
      * Returns a constant indicating the current data connection state
@@ -5125,14 +5149,21 @@
      * @see #DATA_CONNECTING
      * @see #DATA_CONNECTED
      * @see #DATA_SUSPENDED
+     * @see #DATA_DISCONNECTING
      */
     public int getDataState() {
         try {
             ITelephony telephony = getITelephony();
             if (telephony == null)
                 return DATA_DISCONNECTED;
-            return telephony.getDataStateForSubId(
+            int state = telephony.getDataStateForSubId(
                     getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
+            if (state == TelephonyManager.DATA_DISCONNECTING
+                    && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+                return TelephonyManager.DATA_CONNECTED;
+            }
+
+            return state;
         } catch (RemoteException ex) {
             // the phone process is restarting.
             return DATA_DISCONNECTED;
@@ -5153,6 +5184,7 @@
             case DATA_CONNECTING: return "CONNECTING";
             case DATA_CONNECTED: return "CONNECTED";
             case DATA_SUSPENDED: return "SUSPENDED";
+            case DATA_DISCONNECTING: return "DISCONNECTING";
         }
         return "UNKNOWN(" + state + ")";
     }
@@ -6468,75 +6500,6 @@
     }
 
     /**
-     * Sets a per-phone telephony property with the value specified.
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static void setTelephonyProperty(int phoneId, String property, String value) {
-        String propVal = "";
-        String p[] = null;
-        String prop = SystemProperties.get(property);
-
-        if (value == null) {
-            value = "";
-        }
-        value.replace(',', ' ');
-        if (prop != null) {
-            p = prop.split(",");
-        }
-
-        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
-            Rlog.d(TAG, "setTelephonyProperty: invalid phoneId=" + phoneId +
-                    " property=" + property + " value: " + value + " prop=" + prop);
-            return;
-        }
-
-        for (int i = 0; i < phoneId; i++) {
-            String str = "";
-            if ((p != null) && (i < p.length)) {
-                str = p[i];
-            }
-            propVal = propVal + str + ",";
-        }
-
-        propVal = propVal + value;
-        if (p != null) {
-            for (int i = phoneId + 1; i < p.length; i++) {
-                propVal = propVal + "," + p[i];
-            }
-        }
-
-        int propValLen = propVal.length();
-        try {
-            propValLen = propVal.getBytes("utf-8").length;
-        } catch (java.io.UnsupportedEncodingException e) {
-            Rlog.d(TAG, "setTelephonyProperty: utf-8 not supported");
-        }
-        if (propValLen > SystemProperties.PROP_VALUE_MAX) {
-            Rlog.d(TAG, "setTelephonyProperty: property too long phoneId=" + phoneId +
-                    " property=" + property + " value: " + value + " propVal=" + propVal);
-            return;
-        }
-
-        SystemProperties.set(property, propVal);
-    }
-
-    /**
-     * Sets a global telephony property with the value specified.
-     *
-     * @hide
-     */
-    public static void setTelephonyProperty(String property, String value) {
-        if (value == null) {
-            value = "";
-        }
-        Rlog.d(TAG, "setTelephonyProperty: success" + " property=" +
-                property + " value: " + value);
-        SystemProperties.set(property, value);
-    }
-
-    /**
      * Inserts or updates a list property. Expands the list if its length is not enough.
      */
     private static <T> List<T> updateTelephonyProperty(List<T> prop, int phoneId, T value) {
@@ -6873,7 +6836,8 @@
      * If the list is longer than the size of EFfplmn, then the file will be written from the
      * beginning of the list up to the file size.
      *
-     * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * <p>Requires Permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE
+     * MODIFY_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @param fplmns a list of PLMNs to be forbidden.
@@ -6887,7 +6851,7 @@
     public int setForbiddenPlmns(@NonNull List<String> fplmns) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony == null) return 0;
+            if (telephony == null) return -1;
             return telephony.setForbiddenPlmns(
                     getSubId(), APPTYPE_USIM, fplmns, getOpPackageName(), getFeatureId());
         } catch (RemoteException ex) {
@@ -6896,7 +6860,7 @@
             // This could happen before phone starts
             Rlog.e(TAG, "setForbiddenPlmns NullPointerException: " + ex.getMessage());
         }
-        return 0;
+        return -1;
     }
 
     /**
@@ -10335,19 +10299,25 @@
     }
 
     /**
-     * Action set from carrier signalling broadcast receivers to enable/disable radio
-     * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
-     * @param subId the subscription ID that this action applies to.
+     * Carrier action to enable or disable the radio.
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+     *
      * @param enabled control enable or disable radio.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public void carrierActionSetRadioEnabled(int subId, boolean enabled) {
+    public void setRadioEnabled(boolean enabled) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                service.carrierActionSetRadioEnabled(subId, enabled);
+                service.carrierActionSetRadioEnabled(
+                        getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#carrierActionSetRadioEnabled", e);
@@ -10355,20 +10325,25 @@
     }
 
     /**
-     * Action set from carrier signalling broadcast receivers to start/stop reporting default
-     * network available events
-     * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
-     * @param subId the subscription ID that this action applies to.
+     * Carrier action to start or stop reporting default network available events.
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+     *
      * @param report control start/stop reporting network status.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public void carrierActionReportDefaultNetworkStatus(int subId, boolean report) {
+    public void reportDefaultNetworkStatus(boolean report) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                service.carrierActionReportDefaultNetworkStatus(subId, report);
+                service.carrierActionReportDefaultNetworkStatus(
+                        getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), report);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#carrierActionReportDefaultNetworkStatus", e);
@@ -10376,18 +10351,24 @@
     }
 
     /**
-     * Action set from carrier signalling broadcast receivers to reset all carrier actions
-     * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
-     * @param subId the subscription ID that this action applies to.
+     * Reset all carrier actions previously set by {@link #setRadioEnabled},
+     * {@link #reportDefaultNetworkStatus} and {@link #setCarrierDataEnabled}.
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public void carrierActionResetAll(int subId) {
+    public void resetAllCarrierActions() {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                service.carrierActionResetAll(subId);
+                service.carrierActionResetAll(
+                        getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#carrierActionResetAll", e);
@@ -11400,7 +11381,11 @@
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     executor.execute(() -> {
-                        callback.accept(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+                        if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+                            callback.accept(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+                        } else {
+                            callback.accept(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
+                        }
                     });
                 } finally {
                     Binder.restoreCallingIdentity(identity);
@@ -11497,7 +11482,11 @@
                     final long identity = Binder.clearCallingIdentity();
                     try {
                         executor.execute(() -> {
-                            callback.accept(UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION);
+                            if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+                                callback.accept(UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION);
+                            } else {
+                                callback.accept(UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE);
+                            }
                         });
                     } finally {
                         Binder.restoreCallingIdentity(identity);
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index b37d7c7..d3fb37f 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -20,6 +20,8 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -30,6 +32,7 @@
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.RcsFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
 import java.util.concurrent.Executor;
@@ -42,6 +45,8 @@
  * Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this manager.
  * @hide
  */
+@SystemApi
+@TestApi
 public class ImsRcsManager implements RegistrationManager {
     private static final String TAG = "ImsRcsManager";
 
@@ -105,7 +110,7 @@
          *
          * @param capabilities The new availability of the capabilities.
          */
-        public void onAvailabilityChanged(RcsFeature.RcsImsCapabilities capabilities) {
+        public void onAvailabilityChanged(@NonNull RcsFeature.RcsImsCapabilities capabilities) {
         }
 
         /**@hide*/
@@ -142,6 +147,7 @@
 
     /**{@inheritDoc}*/
     @Override
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerImsRegistrationCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull RegistrationCallback c)
@@ -159,6 +165,7 @@
 
     /**{@inheritDoc}*/
     @Override
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void unregisterImsRegistrationCallback(
             @NonNull RegistrationManager.RegistrationCallback c) {
         if (c == null) {
@@ -170,6 +177,7 @@
 
     /**{@inheritDoc}*/
     @Override
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
             @NonNull @ImsRegistrationState Consumer<Integer> stateCallback) {
         if (stateCallback == null) {
@@ -184,6 +192,7 @@
 
     /**{@inheritDoc}*/
     @Override
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
             @NonNull @AccessNetworkConstants.TransportType
                     Consumer<Integer> transportTypeCallback) {
@@ -219,7 +228,7 @@
      * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public void registerRcsAvailabilityCallback(@CallbackExecutor Executor executor,
+    public void registerRcsAvailabilityCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull AvailabilityCallback c) throws ImsException {
         if (c == null) {
             throw new IllegalArgumentException("Must include a non-null AvailabilityCallback.");
@@ -231,13 +240,13 @@
         IImsRcsController imsRcsController = getIImsRcsController();
         if (imsRcsController == null) {
             Log.e(TAG, "Register availability callback: IImsRcsController is null");
-            throw new ImsException("Can not find remote IMS service",
+            throw new ImsException("Cannot find remote IMS service",
                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
 
         c.setExecutor(executor);
         try {
-            imsRcsController.registerRcsAvailabilityCallback(c.getBinder());
+            imsRcsController.registerRcsAvailabilityCallback(mSubId, c.getBinder());
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling IImsRcsController#registerRcsAvailabilityCallback", e);
             throw new ImsException("Remote IMS Service is not available",
@@ -267,12 +276,12 @@
         IImsRcsController imsRcsController = getIImsRcsController();
         if (imsRcsController == null) {
             Log.e(TAG, "Unregister availability callback: IImsRcsController is null");
-            throw new ImsException("Can not find remote IMS service",
+            throw new ImsException("Cannot find remote IMS service",
                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
 
         try {
-            imsRcsController.unregisterRcsAvailabilityCallback(c.getBinder());
+            imsRcsController.unregisterRcsAvailabilityCallback(mSubId, c.getBinder());
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling IImsRcsController#unregisterRcsAvailabilityCallback", e);
             throw new ImsException("Remote IMS Service is not available",
@@ -287,6 +296,9 @@
      * RCS capabilities provided over-the-top by applications.
      *
      * @param capability The RCS capability to query.
+     * @param radioTech The radio tech that this capability failed for, defined as
+     * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or
+     * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}.
      * @return true if the RCS capability is capable for this subscription, false otherwise. This
      * does not necessarily mean that we are registered for IMS and the capability is available, but
      * rather the subscription is capable of this service over IMS.
@@ -297,17 +309,17 @@
      * See {@link ImsException#getCode()} for more information on the error codes.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public boolean isCapable(@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability)
-            throws ImsException {
+    public boolean isCapable(@RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+            @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) throws ImsException {
         IImsRcsController imsRcsController = getIImsRcsController();
         if (imsRcsController == null) {
             Log.e(TAG, "isCapable: IImsRcsController is null");
-            throw new ImsException("Can not find remote IMS service",
+            throw new ImsException("Cannot find remote IMS service",
                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
 
         try {
-            return imsRcsController.isCapable(mSubId, capability);
+            return imsRcsController.isCapable(mSubId, capability, radioTech);
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling IImsRcsController#isCapable", e);
             throw new ImsException("Remote IMS Service is not available",
@@ -336,7 +348,7 @@
         IImsRcsController imsRcsController = getIImsRcsController();
         if (imsRcsController == null) {
             Log.e(TAG, "isAvailable: IImsRcsController is null");
-            throw new ImsException("Can not find remote IMS service",
+            throw new ImsException("Cannot find remote IMS service",
                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
 
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index b379bd0..e81bac0 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -26,9 +26,9 @@
  * {@hide}
  */
 interface IImsRcsController {
-    void registerRcsAvailabilityCallback(IImsCapabilityCallback c);
-    void unregisterRcsAvailabilityCallback(IImsCapabilityCallback c);
-    boolean isCapable(int subId, int capability);
+    void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
+    void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
+    boolean isCapable(int subId, int capability, int radioTech);
     boolean isAvailable(int subId, int capability);
 
     // ImsUceAdapter specific
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 72390d0..4cb8575 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -22,7 +22,6 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.os.IInterface;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
@@ -31,6 +30,7 @@
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.RemoteCallbackListExt;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -310,12 +310,12 @@
     /** @hide */
     protected final Object mLock = new Object();
 
-    private final RemoteCallbackList<IImsFeatureStatusCallback> mStatusCallbacks =
-            new RemoteCallbackList<>();
+    private final RemoteCallbackListExt<IImsFeatureStatusCallback> mStatusCallbacks =
+            new RemoteCallbackListExt<>();
     private @ImsState int mState = STATE_UNAVAILABLE;
     private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-    private final RemoteCallbackList<IImsCapabilityCallback> mCapabilityCallbacks =
-            new RemoteCallbackList<>();
+    private final RemoteCallbackListExt<IImsCapabilityCallback> mCapabilityCallbacks =
+            new RemoteCallbackListExt<>();
     private Capabilities mCapabilityStatus = new Capabilities();
 
     /**
@@ -391,7 +391,7 @@
      * Internal method called by ImsFeature when setFeatureState has changed.
      */
     private void notifyFeatureState(@ImsState int state) {
-        mStatusCallbacks.broadcast((c) -> {
+        mStatusCallbacks.broadcastAction((c) -> {
             try {
                 c.notifyImsFeatureStatus(state);
             } catch (RemoteException e) {
@@ -470,7 +470,7 @@
         synchronized (mLock) {
             mCapabilityStatus = caps.copy();
         }
-        mCapabilityCallbacks.broadcast((callback) -> {
+        mCapabilityCallbacks.broadcastAction((callback) -> {
             try {
                 callback.onCapabilitiesStatusChanged(caps.mCapabilities);
             } catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 0eaf8dc..884a0bc 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -193,7 +193,6 @@
      * of the capability and notify the capability status as true using
      * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the
      * framework that the capability is available for usage.
-     * @hide
      */
     public static class RcsImsCapabilities extends Capabilities {
         /** @hide*/
@@ -207,7 +206,6 @@
 
         /**
          * Undefined capability type for initialization
-         * @hide
          */
         public static final int CAPABILITY_TYPE_NONE = 0;
 
@@ -215,7 +213,6 @@
          * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the
          * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS.
          * If not set, this RcsFeature should not service capability requests.
-         * @hide
          */
         public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1 << 0;
 
@@ -224,33 +221,27 @@
          * framework. If set, the RcsFeature should support capability exchange using a presence
          * server. If not set, this RcsFeature should not publish capabilities or service capability
          * requests using presence.
-         * @hide
          */
         public static final int CAPABILITY_TYPE_PRESENCE_UCE =  1 << 1;
 
-        /**@hide*/
         public RcsImsCapabilities(@RcsImsCapabilityFlag int capabilities) {
             super(capabilities);
         }
 
-        /**@hide*/
         private RcsImsCapabilities(Capabilities c) {
             super(c.getMask());
         }
 
-        /**@hide*/
         @Override
         public void addCapabilities(@RcsImsCapabilityFlag int capabilities) {
             super.addCapabilities(capabilities);
         }
 
-        /**@hide*/
         @Override
         public void removeCapabilities(@RcsImsCapabilityFlag int capabilities) {
             super.removeCapabilities(capabilities);
         }
 
-        /**@hide*/
         @Override
         public boolean isCapable(@RcsImsCapabilityFlag int capabilities) {
             return super.isCapable(capabilities);
@@ -295,10 +286,9 @@
      * set, the {@link RcsFeature} has brought up the capability and is ready for framework
      * requests. To change the status of the capabilities
      * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called.
-     * @hide
      */
     @Override
-    public final RcsImsCapabilities queryCapabilityStatus() {
+    public @NonNull final RcsImsCapabilities queryCapabilityStatus() {
         return new RcsImsCapabilities(super.queryCapabilityStatus());
     }
 
@@ -306,7 +296,6 @@
      * Notify the framework that the capabilities status has changed. If a capability is enabled,
      * this signals to the framework that the capability has been initialized and is ready.
      * Call {@link #queryCapabilityStatus()} to return the current capability status.
-     * @hide
      */
     public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities c) {
         if (c == null) {
@@ -321,7 +310,6 @@
      * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} to
      * enable or disable capability A, this method should return the correct configuration for
      * capability A afterwards (until it has changed).
-     * @hide
      */
     public boolean queryCapabilityConfiguration(
             @RcsImsCapabilities.RcsImsCapabilityFlag int capability,
@@ -343,11 +331,10 @@
      * If for some reason one or more of these capabilities can not be enabled/disabled,
      * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError(int, int, int)} should
      * be called for each capability change that resulted in an error.
-     * @hide
      */
     @Override
-    public void changeEnabledCapabilities(CapabilityChangeRequest request,
-            CapabilityCallbackProxy c) {
+    public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request,
+            @NonNull CapabilityCallbackProxy c) {
         // Base Implementation - Override to provide functionality
     }
 
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index 4c0de7f..d18e93c 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -21,7 +21,6 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.os.PersistableBundle;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsConfigCallback;
@@ -29,6 +28,7 @@
 
 import com.android.ims.ImsConfig;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.RemoteCallbackListExt;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -248,7 +248,8 @@
     })
     public @interface SetConfigResult {}
 
-    private final RemoteCallbackList<IImsConfigCallback> mCallbacks = new RemoteCallbackList<>();
+    private final RemoteCallbackListExt<IImsConfigCallback> mCallbacks =
+            new RemoteCallbackListExt<>();
     ImsConfigStub mImsConfigStub;
 
     /**
@@ -289,7 +290,7 @@
         if (mCallbacks == null) {
             return;
         }
-        mCallbacks.broadcast(c -> {
+        mCallbacks.broadcastAction(c -> {
             try {
                 c.onIntConfigChanged(item, value);
             } catch (RemoteException e) {
@@ -303,7 +304,7 @@
         if (mCallbacks == null) {
             return;
         }
-        mCallbacks.broadcast(c -> {
+        mCallbacks.broadcastAction(c -> {
             try {
                 c.onStringConfigChanged(item, value);
             } catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index c0f16e5..14a64d2 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -20,7 +20,6 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.net.Uri;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.RegistrationManager;
@@ -29,6 +28,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.RemoteCallbackListExt;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -94,8 +94,8 @@
         }
     };
 
-    private final RemoteCallbackList<IImsRegistrationCallback> mCallbacks
-            = new RemoteCallbackList<>();
+    private final RemoteCallbackListExt<IImsRegistrationCallback> mCallbacks =
+            new RemoteCallbackListExt<>();
     private final Object mLock = new Object();
     // Locked on mLock
     private @ImsRegistrationTech
@@ -129,7 +129,7 @@
      */
     public final void onRegistered(@ImsRegistrationTech int imsRadioTech) {
         updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERED);
-        mCallbacks.broadcast((c) -> {
+        mCallbacks.broadcastAction((c) -> {
             try {
                 c.onRegistered(imsRadioTech);
             } catch (RemoteException e) {
@@ -147,7 +147,7 @@
      */
     public final void onRegistering(@ImsRegistrationTech int imsRadioTech) {
         updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERING);
-        mCallbacks.broadcast((c) -> {
+        mCallbacks.broadcastAction((c) -> {
             try {
                 c.onRegistering(imsRadioTech);
             } catch (RemoteException e) {
@@ -175,7 +175,7 @@
      */
     public final void onDeregistered(ImsReasonInfo info) {
         updateToDisconnectedState(info);
-        mCallbacks.broadcast((c) -> {
+        mCallbacks.broadcastAction((c) -> {
             try {
                 c.onDeregistered(info);
             } catch (RemoteException e) {
@@ -194,7 +194,7 @@
      */
     public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
             ImsReasonInfo info) {
-        mCallbacks.broadcast((c) -> {
+        mCallbacks.broadcastAction((c) -> {
             try {
                 c.onTechnologyChangeFailed(imsRadioTech, info);
             } catch (RemoteException e) {
@@ -210,7 +210,7 @@
      * @param uris
      */
     public final void onSubscriberAssociatedUriChanged(Uri[] uris) {
-        mCallbacks.broadcast((c) -> {
+        mCallbacks.broadcastAction((c) -> {
             try {
                 c.onSubscriberAssociatedUriChanged(uris);
             } catch (RemoteException e) {
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index ac4c8ec..9f4d066 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -594,4 +594,12 @@
      * @return true for success, false otherwise.
      */
     boolean setSmscAddressOnIccEfForSubscriber(String smsc, int subId, String callingPackage);
+
+    /**
+     * Get the capacity count of sms on Icc card.
+     *
+     * @param subId for subId which getSmsCapacityOnIcc is queried.
+     * @return capacity of ICC
+     */
+    int getSmsCapacityOnIccForSubscriber(int subId);
 }
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 9865f76..2430d82 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -211,4 +211,9 @@
             String smsc, int subId, String callingPackage) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public int getSmsCapacityOnIccForSubscriber(int subId) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ec54ec..97b24ae 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -888,12 +888,13 @@
     /**
     *  @return true if the ImsService to bind to for the slot id specified was set, false otherwise.
     */
-    boolean setImsService(int slotId, boolean isCarrierImsService, String packageName);
+    boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
+            in int[] featureTypes, in String packageName);
 
     /**
     * @return the package name of the carrier/device ImsService associated with this slot.
     */
-    String getImsService(int slotId, boolean isCarrierImsService);
+    String getBoundImsServicePackage(int slotIndex, boolean isCarrierImsService, int featureType);
 
     /**
      * Get the MmTelFeature state attached to this subscription id.
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
index f7f0f29..8640acc 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
@@ -16,14 +16,10 @@
 
 package com.android.internal.telephony;
 
-import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
-import android.telephony.PreciseCallState;
 
 import com.android.internal.telephony.PhoneConstants;
 
-import java.util.List;
-
 public class PhoneConstantConversions {
     /**
      * Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
@@ -67,6 +63,8 @@
                 return TelephonyManager.DATA_CONNECTED;
             case SUSPENDED:
                 return TelephonyManager.DATA_SUSPENDED;
+            case DISCONNECTING:
+                return TelephonyManager.DATA_DISCONNECTING;
             default:
                 return TelephonyManager.DATA_DISCONNECTED;
         }
@@ -84,6 +82,8 @@
                 return PhoneConstants.DataState.CONNECTED;
             case TelephonyManager.DATA_SUSPENDED:
                 return PhoneConstants.DataState.SUSPENDED;
+            case TelephonyManager.DATA_DISCONNECTING:
+                return PhoneConstants.DataState.DISCONNECTING;
             default:
                 return PhoneConstants.DataState.DISCONNECTED;
         }
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index fde2c5a..fadb573 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -46,6 +46,7 @@
       * <ul>
       * <li>CONNECTED = IP traffic should be available</li>
       * <li>CONNECTING = Currently setting up data connection</li>
+      * <li>DISCONNECTING = IP temporarily available</li>
       * <li>DISCONNECTED = IP not available</li>
       * <li>SUSPENDED = connection is created but IP traffic is
       *                 temperately not available. i.e. voice call is in place
@@ -55,10 +56,15 @@
     @UnsupportedAppUsage(implicitMember =
             "values()[Lcom/android/internal/telephony/PhoneConstants$DataState;")
     public enum DataState {
-        @UnsupportedAppUsage CONNECTED,
-        @UnsupportedAppUsage CONNECTING,
-        @UnsupportedAppUsage DISCONNECTED,
-        @UnsupportedAppUsage SUSPENDED;
+        @UnsupportedAppUsage
+        CONNECTED,
+        @UnsupportedAppUsage
+        CONNECTING,
+        @UnsupportedAppUsage
+        DISCONNECTED,
+        @UnsupportedAppUsage
+        SUSPENDED,
+        DISCONNECTING;
     };
 
     public static final String STATE_KEY = "state";
@@ -91,20 +97,12 @@
 
     public static final String PHONE_NAME_KEY = "phoneName";
     public static final String DATA_NETWORK_TYPE_KEY = "networkType";
-    public static final String DATA_FAILURE_CAUSE_KEY = "failCause";
     public static final String DATA_APN_TYPE_KEY = "apnType";
     public static final String DATA_APN_KEY = "apn";
-    public static final String DATA_LINK_PROPERTIES_KEY = "linkProperties";
-    public static final String DATA_NETWORK_CAPABILITIES_KEY = "networkCapabilities";
 
-    public static final String DATA_IFACE_NAME_KEY = "iface";
-    public static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable";
-    public static final String DATA_NETWORK_ROAMING_KEY = "networkRoaming";
     public static final String PHONE_IN_ECM_STATE = "phoneinECMState";
     public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall";
 
-    public static final String REASON_LINK_PROPERTIES_CHANGED = "linkPropertiesChanged";
-
     /**
      * Return codes for supplyPinReturnResult and
      * supplyPukReturnResult APIs
diff --git a/telephony/java/com/android/internal/telephony/util/RemoteCallbackListExt.java b/telephony/java/com/android/internal/telephony/util/RemoteCallbackListExt.java
new file mode 100644
index 0000000..d66bda9
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/util/RemoteCallbackListExt.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.util;
+
+import android.os.IInterface;
+import android.os.RemoteCallbackList;
+
+import java.util.function.Consumer;
+
+/**
+ * Extension of RemoteCallbackList
+ * @param <E> defines the type of registered callbacks
+ */
+public class RemoteCallbackListExt<E extends IInterface> extends RemoteCallbackList<E> {
+    /**
+     * Performs {@code action} on each callback, calling
+     * {@link RemoteCallbackListExt#beginBroadcast()}
+     * /{@link RemoteCallbackListExt#finishBroadcast()} before/after looping
+     * @param action to be performed on each callback
+     *
+     */
+    public void broadcastAction(Consumer<E> action) {
+        int itemCount = beginBroadcast();
+        try {
+            for (int i = 0; i < itemCount; i++) {
+                action.accept(getBroadcastItem(i));
+            }
+        } finally {
+            finishBroadcast();
+        }
+    }
+}
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 616b6b0..248c117 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -28,7 +28,7 @@
         ":framework_native_aidl",
     ],
     libs: [
-        "framework-all",
+        "framework",
         "app-compat-annotations",
         "unsupportedappusage",
     ],
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 0208c3a..9d913b9 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -16,6 +16,7 @@
 
 package android.test.mock;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
@@ -211,6 +212,15 @@
         throw new UnsupportedOperationException();
     }
 
+    /**
+     * {@inheritDoc Context#getCrateDir()}
+     * @hide
+     */
+    @Override
+    public File getCrateDir(@NonNull String crateId) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public File getNoBackupFilesDir() {
         throw new UnsupportedOperationException();
diff --git a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
index e323592..026677e 100644
--- a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
+++ b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
@@ -53,6 +53,8 @@
      */
     @Test
     public void testCreateStartDelete() throws Exception {
+        // Disable package verifier for ADB installs.
+        getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
         int iteration = 0;
         final long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(TIME_LIMIT_MINUTES);
         while (System.nanoTime() < deadline) {
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index abe6c61..daa85bd 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -1098,4 +1098,28 @@
             InstallUtils.dropShellPermissionIdentity();
         }
     }
+
+    /**
+     * Test we can't enable rollback for non-whitelisted app without
+     * TEST_MANAGE_ROLLBACKS permission
+     */
+    @Test
+    public void testNonRollbackWhitelistedApp() throws Exception {
+        try {
+            InstallUtils.adoptShellPermissionIdentity(
+                    Manifest.permission.INSTALL_PACKAGES,
+                    Manifest.permission.DELETE_PACKAGES,
+                    Manifest.permission.MANAGE_ROLLBACKS);
+
+            Uninstall.packages(TestApp.A);
+            Install.single(TestApp.A1).commit();
+            assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+
+            Install.single(TestApp.A2).setEnableRollback().commit();
+            Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+            assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+        } finally {
+            InstallUtils.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 7b289d8..879ac64 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -40,6 +40,7 @@
 import com.android.cts.install.lib.Uninstall;
 import com.android.cts.rollback.lib.Rollback;
 import com.android.cts.rollback.lib.RollbackUtils;
+import com.android.internal.R;
 
 import libcore.io.IoUtils;
 
@@ -341,6 +342,37 @@
                         getNetworkStackPackageName())).isNull();
     }
 
+    private static String getModuleMetadataPackageName() {
+        return InstrumentationRegistry.getInstrumentation().getContext()
+                .getResources().getString(R.string.config_defaultModuleMetadataProvider);
+    }
+
+    @Test
+    public void testRollbackWhitelistedApp_Phase1() throws Exception {
+        // Remove available rollbacks
+        String pkgName = getModuleMetadataPackageName();
+        RollbackUtils.getRollbackManager().expireRollbackForPackage(pkgName);
+        assertThat(RollbackUtils.getAvailableRollback(pkgName)).isNull();
+
+        // Overwrite existing permissions. We don't want TEST_MANAGE_ROLLBACKS which allows us
+        // to enable rollback for any app
+        InstallUtils.adoptShellPermissionIdentity(
+                Manifest.permission.INSTALL_PACKAGES,
+                Manifest.permission.MANAGE_ROLLBACKS);
+
+        // Re-install a whitelisted app with rollbacks enabled
+        String filePath = InstrumentationRegistry.getInstrumentation().getContext()
+                .getPackageManager().getPackageInfo(pkgName, 0).applicationInfo.sourceDir;
+        TestApp app = new TestApp("ModuleMetadata", pkgName, -1, false, new File(filePath));
+        Install.single(app).setStaged().setEnableRollback()
+                .addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit();
+    }
+
+    @Test
+    public void testRollbackWhitelistedApp_Phase2() throws Exception {
+        assertThat(RollbackUtils.getAvailableRollback(getModuleMetadataPackageName())).isNotNull();
+    }
+
     private static void runShellCommand(String cmd) {
         ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .executeShellCommand(cmd);
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index e4a8feb..07d829d 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -175,6 +175,16 @@
         runPhase("testPreviouslyAbandonedRollbacks_Phase3");
     }
 
+    /**
+     * Tests we can enable rollback for a whitelisted app.
+     */
+    @Test
+    public void testRollbackWhitelistedApp() throws Exception {
+        runPhase("testRollbackWhitelistedApp_Phase1");
+        getDevice().reboot();
+        runPhase("testRollbackWhitelistedApp_Phase2");
+    }
+
     private void crashProcess(String processName, int numberOfCrashes) throws Exception {
         String pid = "";
         String lastPid = "invalid";
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
index 5cb0d7e..e632aaf 100644
--- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -22,8 +22,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
-import android.net.SocketKeepalive.InvalidPacketException;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
index 6b4c346..ca1847a 100755
--- a/tools/localedata/extract_icu_data.py
+++ b/tools/localedata/extract_icu_data.py
@@ -176,6 +176,9 @@
     dump_representative_locales(representative_locales)
     return likely_script_dict
 
+def escape_script_variable_name(script):
+    """Escape characters, e.g. '~', in a C++ variable name"""
+    return script.replace("~", "_")
 
 def read_parent_data(icu_data_dir):
     """Read locale parent data from ICU data files."""
@@ -221,7 +224,7 @@
     for script in sorted_scripts:
         parent_dict = script_organized_dict[script]
         print ('const std::unordered_map<uint32_t, uint32_t> %s_PARENTS({'
-            % script.upper())
+            % escape_script_variable_name(script.upper()))
         for locale in sorted(parent_dict.keys()):
             parent = parent_dict[locale]
             print '    {0x%08Xu, 0x%08Xu}, // %s -> %s' % (
@@ -239,7 +242,7 @@
     for script in sorted_scripts:
         print "    {{'%c', '%c', '%c', '%c'}, &%s_PARENTS}," % (
             script[0], script[1], script[2], script[3],
-            script.upper())
+            escape_script_variable_name(script.upper()))
     print '};'
 
 
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index 7733761..15c3278 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -21,6 +21,7 @@
     name: "stats-log-api-gen",
     srcs: [
         "Collation.cpp",
+        "atoms_info_writer.cpp",
         "java_writer.cpp",
         "java_writer_q.cpp",
         "main.cpp",
@@ -102,13 +103,19 @@
 cc_library {
     name: "libstatslog",
     host_supported: true,
-    generated_sources: ["statslog.cpp"],
-    generated_headers: ["statslog.h"],
+    generated_sources: [
+        "statslog.cpp",
+    ],
+    generated_headers: [
+        "statslog.h"
+    ],
     cflags: [
         "-Wall",
         "-Werror",
     ],
-    export_generated_headers: ["statslog.h"],
+    export_generated_headers: [
+        "statslog.h"
+    ],
     shared_libs: [
         "liblog",
         "libcutils",
@@ -127,3 +134,4 @@
         },
     },
 }
+
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 373adca..fa55601 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -379,6 +379,7 @@
   int errorCount = 0;
   const bool dbg = false;
 
+  int maxPushedAtomId = 2;
   for (int i = 0; i < descriptor->field_count(); i++) {
     const FieldDescriptor *atomField = descriptor->field(i);
 
@@ -447,8 +448,14 @@
         }
         atoms->non_chained_decls.insert(nonChainedAtomDecl);
     }
+
+    if (atomDecl.code < PULL_ATOM_START_ID && atomDecl.code > maxPushedAtomId) {
+        maxPushedAtomId = atomDecl.code;
+    }
   }
 
+  atoms->maxPushedAtomId = maxPushedAtomId;
+
   if (dbg) {
     printf("signatures = [\n");
     for (map<vector<java_type_t>, set<string>>::const_iterator it =
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 44746c9..3efdd52 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -111,6 +111,7 @@
     set<AtomDecl> decls;
     set<AtomDecl> non_chained_decls;
     map<vector<java_type_t>, set<string>> non_chained_signatures_to_modules;
+    int maxPushedAtomId;
 };
 
 /**
@@ -123,4 +124,4 @@
 }  // namespace android
 
 
-#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
\ No newline at end of file
+#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
new file mode 100644
index 0000000..54a9982
--- /dev/null
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "atoms_info_writer.h"
+#include "utils.h"
+
+#include <map>
+#include <set>
+#include <vector>
+
+namespace android {
+namespace stats_log_api_gen {
+
+static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) {
+    fprintf(out, "struct StateAtomFieldOptions {\n");
+    fprintf(out, "  std::vector<int> primaryFields;\n");
+    fprintf(out, "  int exclusiveField;\n");
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "struct AtomsInfo {\n");
+    fprintf(out,
+            "  const static std::set<int> "
+            "kTruncatingTimestampAtomBlackList;\n");
+    fprintf(out, "  const static std::map<int, int> kAtomsWithUidField;\n");
+    fprintf(out,
+            "  const static std::set<int> kAtomsWithAttributionChain;\n");
+    fprintf(out,
+            "  const static std::map<int, StateAtomFieldOptions> "
+            "kStateAtomsFieldOptions;\n");
+    fprintf(out,
+            "  const static std::map<int, std::vector<int>> "
+            "kBytesFieldAtoms;\n");
+    fprintf(out,
+            "  const static std::set<int> kWhitelistedAtoms;\n");
+    fprintf(out, "};\n");
+    fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n", atoms.maxPushedAtomId);
+
+}
+
+static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) {
+    std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed",
+                                                 "audio_state_changed",
+                                                 "call_state_changed",
+                                                 "phone_signal_strength_changed",
+                                                 "mobile_bytes_transfer_by_fg_bg",
+                                                 "mobile_bytes_transfer"};
+    fprintf(out,
+            "const std::set<int> "
+            "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n");
+    for (set<string>::const_iterator blacklistedAtom = kTruncatingAtomNames.begin();
+         blacklistedAtom != kTruncatingAtomNames.end(); blacklistedAtom++) {
+            fprintf(out, " %s,\n", make_constant_name(*blacklistedAtom).c_str());
+    }
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
+    fprintf(out,
+            "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n");
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+         atom != atoms.decls.end(); atom++) {
+        for (vector<AtomField>::const_iterator field = atom->fields.begin();
+             field != atom->fields.end(); field++) {
+            if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+                string constant = make_constant_name(atom->name);
+                fprintf(out, " %s,\n", constant.c_str());
+                break;
+            }
+        }
+    }
+
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
+    fprintf(out,
+            "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+         atom != atoms.decls.end(); atom++) {
+        if (atom->whitelisted) {
+            string constant = make_constant_name(atom->name);
+            fprintf(out, " %s,\n", constant.c_str());
+        }
+    }
+
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
+    fprintf(out, "  std::map<int, int> uidField;\n");
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+         atom != atoms.decls.end(); atom++) {
+        if (atom->uidField == 0) {
+            continue;
+        }
+        fprintf(out,
+                "\n    // Adding uid field for atom "
+                "(%d)%s\n",
+                atom->code, atom->name.c_str());
+        fprintf(out, "    uidField[static_cast<int>(%s)] = %d;\n",
+                make_constant_name(atom->name).c_str(), atom->uidField);
+    }
+
+    fprintf(out, "    return uidField;\n");
+    fprintf(out, "};\n");
+
+    fprintf(out,
+            "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
+            "getAtomUidField();\n");
+
+    fprintf(out,
+            "static std::map<int, StateAtomFieldOptions> "
+            "getStateAtomFieldOptions() {\n");
+    fprintf(out, "    std::map<int, StateAtomFieldOptions> options;\n");
+    fprintf(out, "    StateAtomFieldOptions opt;\n");
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+         atom != atoms.decls.end(); atom++) {
+        if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
+            continue;
+        }
+        fprintf(out,
+                "\n    // Adding primary and exclusive fields for atom "
+                "(%d)%s\n",
+                atom->code, atom->name.c_str());
+        fprintf(out, "    opt.primaryFields.clear();\n");
+        for (const auto& field : atom->primaryFields) {
+            fprintf(out, "    opt.primaryFields.push_back(%d);\n", field);
+        }
+
+        fprintf(out, "    opt.exclusiveField = %d;\n", atom->exclusiveField);
+        fprintf(out, "    options[static_cast<int>(%s)] = opt;\n",
+                make_constant_name(atom->name).c_str());
+    }
+
+    fprintf(out, "    return options;\n");
+    fprintf(out, "}\n");
+
+    fprintf(out,
+            "const std::map<int, StateAtomFieldOptions> "
+            "AtomsInfo::kStateAtomsFieldOptions = "
+            "getStateAtomFieldOptions();\n");
+
+    fprintf(out,
+            "static std::map<int, std::vector<int>> "
+            "getBinaryFieldAtoms() {\n");
+    fprintf(out, "    std::map<int, std::vector<int>> options;\n");
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+         atom != atoms.decls.end(); atom++) {
+        if (atom->binaryFields.size() == 0) {
+            continue;
+        }
+        fprintf(out,
+                "\n    // Adding binary fields for atom "
+                "(%d)%s\n",
+                atom->code, atom->name.c_str());
+
+        for (const auto& field : atom->binaryFields) {
+            fprintf(out, "    options[static_cast<int>(%s)].push_back(%d);\n",
+                    make_constant_name(atom->name).c_str(), field);
+        }
+    }
+
+    fprintf(out, "    return options;\n");
+    fprintf(out, "}\n");
+
+    fprintf(out,
+            "const std::map<int, std::vector<int>> "
+            "AtomsInfo::kBytesFieldAtoms = "
+            "getBinaryFieldAtoms();\n");
+
+}
+
+int write_atoms_info_header(FILE* out, const Atoms &atoms, const string& namespaceStr) {
+    // Print prelude
+    fprintf(out, "// This file is autogenerated\n");
+    fprintf(out, "\n");
+    fprintf(out, "#pragma once\n");
+    fprintf(out, "\n");
+    fprintf(out, "#include <vector>\n");
+    fprintf(out, "#include <map>\n");
+    fprintf(out, "#include <set>\n");
+    fprintf(out, "\n");
+
+    write_namespace(out, namespaceStr);
+
+    write_atoms_info_header_body(out, atoms);
+
+    fprintf(out, "\n");
+    write_closing_namespace(out, namespaceStr);
+
+    return 0;
+}
+
+int write_atoms_info_cpp(FILE *out, const Atoms &atoms, const string& namespaceStr,
+        const string& importHeader, const string& statslogHeader) {
+    // Print prelude
+    fprintf(out, "// This file is autogenerated\n");
+    fprintf(out, "\n");
+    fprintf(out, "#include <%s>\n", importHeader.c_str());
+    fprintf(out, "#include <%s>\n", statslogHeader.c_str());
+    fprintf(out, "\n");
+
+    write_namespace(out, namespaceStr);
+
+    write_atoms_info_cpp_body(out, atoms);
+
+    // Print footer
+    fprintf(out, "\n");
+    write_closing_namespace(out, namespaceStr);
+
+    return 0;
+}
+
+}  // namespace stats_log_api_gen
+}  // namespace android
diff --git a/tools/stats_log_api_gen/atoms_info_writer.h b/tools/stats_log_api_gen/atoms_info_writer.h
new file mode 100644
index 0000000..bc67782
--- /dev/null
+++ b/tools/stats_log_api_gen/atoms_info_writer.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "Collation.h"
+
+#include <stdio.h>
+#include <string.h>
+
+namespace android {
+namespace stats_log_api_gen {
+
+using namespace std;
+
+int write_atoms_info_cpp(FILE* out, const Atoms& atoms, const string& namespaceStr,
+        const string& importHeader, const string& statslogHeader
+);
+
+int write_atoms_info_header(FILE* out, const Atoms& atoms, const string& namespaceStr);
+
+}  // namespace stats_log_api_gen
+}  // namespace android
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index bc6d82a..ad171da 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -1,6 +1,7 @@
 
 
 #include "Collation.h"
+#include "atoms_info_writer.h"
 #if !defined(STATS_SCHEMA_LEGACY)
 #include "java_writer.h"
 #endif
@@ -18,8 +19,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "android-base/strings.h"
-
 using namespace google::protobuf;
 using namespace std;
 
@@ -28,152 +27,6 @@
 
 using android::os::statsd::Atom;
 
-static void write_atoms_info_cpp(FILE *out, const Atoms &atoms) {
-    std::set<string> kTruncatingAtomNames = {"mobile_radio_power_state_changed",
-                                                 "audio_state_changed",
-                                                 "call_state_changed",
-                                                 "phone_signal_strength_changed",
-                                                 "mobile_bytes_transfer_by_fg_bg",
-                                                 "mobile_bytes_transfer"};
-    fprintf(out,
-            "const std::set<int> "
-            "AtomsInfo::kTruncatingTimestampAtomBlackList = {\n");
-    for (set<string>::const_iterator blacklistedAtom = kTruncatingAtomNames.begin();
-         blacklistedAtom != kTruncatingAtomNames.end(); blacklistedAtom++) {
-            fprintf(out, " %s,\n", make_constant_name(*blacklistedAtom).c_str());
-    }
-    fprintf(out, "};\n");
-    fprintf(out, "\n");
-
-    fprintf(out,
-            "const std::set<int> AtomsInfo::kAtomsWithAttributionChain = {\n");
-    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
-         atom != atoms.decls.end(); atom++) {
-        for (vector<AtomField>::const_iterator field = atom->fields.begin();
-             field != atom->fields.end(); field++) {
-            if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-                string constant = make_constant_name(atom->name);
-                fprintf(out, " %s,\n", constant.c_str());
-                break;
-            }
-        }
-    }
-
-    fprintf(out, "};\n");
-    fprintf(out, "\n");
-
-    fprintf(out,
-            "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n");
-    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
-         atom != atoms.decls.end(); atom++) {
-        if (atom->whitelisted) {
-            string constant = make_constant_name(atom->name);
-            fprintf(out, " %s,\n", constant.c_str());
-        }
-    }
-
-    fprintf(out, "};\n");
-    fprintf(out, "\n");
-
-    fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
-    fprintf(out, "  std::map<int, int> uidField;\n");
-    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
-         atom != atoms.decls.end(); atom++) {
-        if (atom->uidField == 0) {
-            continue;
-        }
-        fprintf(out,
-                "\n    // Adding uid field for atom "
-                "(%d)%s\n",
-                atom->code, atom->name.c_str());
-        fprintf(out, "    uidField[static_cast<int>(%s)] = %d;\n",
-                make_constant_name(atom->name).c_str(), atom->uidField);
-    }
-
-    fprintf(out, "    return uidField;\n");
-    fprintf(out, "};\n");
-
-    fprintf(out,
-            "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
-            "getAtomUidField();\n");
-
-    fprintf(out,
-            "static std::map<int, StateAtomFieldOptions> "
-            "getStateAtomFieldOptions() {\n");
-    fprintf(out, "    std::map<int, StateAtomFieldOptions> options;\n");
-    fprintf(out, "    StateAtomFieldOptions opt;\n");
-    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
-         atom != atoms.decls.end(); atom++) {
-        if (atom->primaryFields.size() == 0 && atom->exclusiveField == 0) {
-            continue;
-        }
-        fprintf(out,
-                "\n    // Adding primary and exclusive fields for atom "
-                "(%d)%s\n",
-                atom->code, atom->name.c_str());
-        fprintf(out, "    opt.primaryFields.clear();\n");
-        for (const auto& field : atom->primaryFields) {
-            fprintf(out, "    opt.primaryFields.push_back(%d);\n", field);
-        }
-
-        fprintf(out, "    opt.exclusiveField = %d;\n", atom->exclusiveField);
-        fprintf(out, "    options[static_cast<int>(%s)] = opt;\n",
-                make_constant_name(atom->name).c_str());
-    }
-
-    fprintf(out, "    return options;\n");
-    fprintf(out, "}\n");
-
-    fprintf(out,
-            "const std::map<int, StateAtomFieldOptions> "
-            "AtomsInfo::kStateAtomsFieldOptions = "
-            "getStateAtomFieldOptions();\n");
-
-    fprintf(out,
-            "static std::map<int, std::vector<int>> "
-            "getBinaryFieldAtoms() {\n");
-    fprintf(out, "    std::map<int, std::vector<int>> options;\n");
-    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
-         atom != atoms.decls.end(); atom++) {
-        if (atom->binaryFields.size() == 0) {
-            continue;
-        }
-        fprintf(out,
-                "\n    // Adding binary fields for atom "
-                "(%d)%s\n",
-                atom->code, atom->name.c_str());
-
-        for (const auto& field : atom->binaryFields) {
-            fprintf(out, "    options[static_cast<int>(%s)].push_back(%d);\n",
-                    make_constant_name(atom->name).c_str(), field);
-        }
-    }
-
-    fprintf(out, "    return options;\n");
-    fprintf(out, "}\n");
-
-    fprintf(out,
-            "const std::map<int, std::vector<int>> "
-            "AtomsInfo::kBytesFieldAtoms = "
-            "getBinaryFieldAtoms();\n");
-}
-
-// Writes namespaces for the cpp and header files, returning the number of namespaces written.
-void write_namespace(FILE* out, const string& cppNamespaces) {
-    vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
-    for (string cppNamespace : cppNamespaceVec) {
-        fprintf(out, "namespace %s {\n", cppNamespace.c_str());
-    }
-}
-
-// Writes namespace closing brackets for cpp and header files.
-void write_closing_namespace(FILE* out, const string& cppNamespaces) {
-    vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
-    for (auto it = cppNamespaceVec.rbegin(); it != cppNamespaceVec.rend(); ++it) {
-        fprintf(out, "} // namespace %s\n", it->c_str());
-    }
-}
-
 static int write_stats_log_cpp(FILE *out, const Atoms &atoms, const AtomDecl &attributionDecl,
                                const string& moduleName, const string& cppNamespace,
                                const string& importHeader) {
@@ -202,11 +55,6 @@
     fprintf(out, "const static bool kStatsdEnabled = false;\n");
     fprintf(out, "#endif\n");
 
-    // AtomsInfo is only used by statsd internally and is not needed for other modules.
-    if (moduleName == DEFAULT_MODULE_NAME) {
-        write_atoms_info_cpp(out, atoms);
-    }
-
     fprintf(out, "int64_t lastRetryTimestampNs = -1;\n");
     fprintf(out, "const int64_t kMinRetryIntervalNs = NS_PER_SEC * 60 * 20; // 20 minutes\n");
     fprintf(out, "static std::mutex mLogdRetryMutex;\n");
@@ -543,42 +391,6 @@
     return 0;
 }
 
-static void write_cpp_usage(
-    FILE* out, const string& method_name, const string& atom_code_name,
-    const AtomDecl& atom, const AtomDecl &attributionDecl) {
-    fprintf(out, "     * Usage: %s(StatsLog.%s", method_name.c_str(),
-            atom_code_name.c_str());
-
-    for (vector<AtomField>::const_iterator field = atom.fields.begin();
-            field != atom.fields.end(); field++) {
-        if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
-            for (auto chainField : attributionDecl.fields) {
-                if (chainField.javaType == JAVA_TYPE_STRING) {
-                    fprintf(out, ", const std::vector<%s>& %s",
-                         cpp_type_name(chainField.javaType),
-                         chainField.name.c_str());
-                } else {
-                    fprintf(out, ", const %s* %s, size_t %s_length",
-                         cpp_type_name(chainField.javaType),
-                         chainField.name.c_str(), chainField.name.c_str());
-                }
-            }
-        } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
-            fprintf(out, ", const std::map<int, int32_t>& %s_int"
-                         ", const std::map<int, int64_t>& %s_long"
-                         ", const std::map<int, char const*>& %s_str"
-                         ", const std::map<int, float>& %s_float",
-                         field->name.c_str(),
-                         field->name.c_str(),
-                         field->name.c_str(),
-                         field->name.c_str());
-        } else {
-            fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
-        }
-    }
-    fprintf(out, ");\n");
-}
-
 static void write_cpp_method_header(
         FILE* out,
         const string& method_name,
@@ -645,45 +457,8 @@
     fprintf(out, " * API For logging statistics events.\n");
     fprintf(out, " */\n");
     fprintf(out, "\n");
-    fprintf(out, "/**\n");
-    fprintf(out, " * Constants for atom codes.\n");
-    fprintf(out, " */\n");
-    fprintf(out, "enum {\n");
 
-    std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
-    build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
-
-    size_t i = 0;
-    int maxPushedAtomId = 2;
-    // Print atom constants
-    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
-        atom != atoms.decls.end(); atom++) {
-        // Skip if the atom is not needed for the module.
-        if (!atom_needed_for_module(*atom, moduleName)) {
-            continue;
-        }
-        string constant = make_constant_name(atom->name);
-        fprintf(out, "\n");
-        fprintf(out, "    /**\n");
-        fprintf(out, "     * %s %s\n", atom->message.c_str(), atom->name.c_str());
-        write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
-
-        auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
-        if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
-            write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
-                attributionDecl);
-        }
-        fprintf(out, "     */\n");
-        char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
-        fprintf(out, "    %s = %d%s\n", constant.c_str(), atom->code, comma);
-        if (atom->code < PULL_ATOM_START_ID && atom->code > maxPushedAtomId) {
-            maxPushedAtomId = atom->code;
-        }
-        i++;
-    }
-    fprintf(out, "\n");
-    fprintf(out, "};\n");
-    fprintf(out, "\n");
+    write_native_atom_constants(out, atoms, attributionDecl, moduleName);
 
     // Print constants for the enum values.
     fprintf(out, "//\n");
@@ -723,36 +498,6 @@
     fprintf(out, "};\n");
     fprintf(out, "\n");
 
-    // This metadata is only used by statsd, which uses the default libstatslog.
-    if (moduleName == DEFAULT_MODULE_NAME) {
-
-        fprintf(out, "struct StateAtomFieldOptions {\n");
-        fprintf(out, "  std::vector<int> primaryFields;\n");
-        fprintf(out, "  int exclusiveField;\n");
-        fprintf(out, "};\n");
-        fprintf(out, "\n");
-
-        fprintf(out, "struct AtomsInfo {\n");
-        fprintf(out,
-                "  const static std::set<int> "
-                "kTruncatingTimestampAtomBlackList;\n");
-        fprintf(out, "  const static std::map<int, int> kAtomsWithUidField;\n");
-        fprintf(out,
-                "  const static std::set<int> kAtomsWithAttributionChain;\n");
-        fprintf(out,
-                "  const static std::map<int, StateAtomFieldOptions> "
-                "kStateAtomsFieldOptions;\n");
-        fprintf(out,
-                "  const static std::map<int, std::vector<int>> "
-                "kBytesFieldAtoms;");
-        fprintf(out,
-                "  const static std::set<int> kWhitelistedAtoms;\n");
-        fprintf(out, "};\n");
-
-        fprintf(out, "const static int kMaxPushedAtomId = %d;\n\n",
-                maxPushedAtomId);
-    }
-
     // Print write methods
     fprintf(out, "//\n");
     fprintf(out, "// Write methods\n");
@@ -1235,15 +980,21 @@
     fprintf(stderr, "usage: stats-log-api-gen OPTIONS\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "OPTIONS\n");
-    fprintf(stderr, "  --cpp FILENAME       the header file to output\n");
-    fprintf(stderr, "  --header FILENAME    the cpp file to output\n");
+    fprintf(stderr, "  --cpp FILENAME       the header file to output for write helpers\n");
+    fprintf(stderr, "  --header FILENAME    the cpp file to output for write helpers\n");
+    fprintf(stderr,
+            "  --atomsInfoCpp FILENAME       the header file to output for statsd metadata\n");
+    fprintf(stderr, "  --atomsInfoHeader FILENAME    the cpp file to output for statsd metadata\n");
     fprintf(stderr, "  --help               this message\n");
     fprintf(stderr, "  --java FILENAME      the java file to output\n");
     fprintf(stderr, "  --jni FILENAME       the jni file to output\n");
     fprintf(stderr, "  --module NAME        optional, module name to generate outputs for\n");
     fprintf(stderr, "  --namespace COMMA,SEP,NAMESPACE   required for cpp/header with module\n");
     fprintf(stderr, "                                    comma separated namespace of the files\n");
-    fprintf(stderr, "  --importHeader NAME  required for cpp/jni to say which header to import\n");
+    fprintf(stderr,"  --importHeader NAME  required for cpp/jni to say which header to import "
+            "for write helpers\n");
+    fprintf(stderr,"  --atomsInfoImportHeader NAME  required for cpp to say which header to import "
+            "for statsd metadata\n");
     fprintf(stderr, "  --javaPackage PACKAGE             the package for the java file.\n");
     fprintf(stderr, "                                    required for java with module\n");
     fprintf(stderr, "  --javaClass CLASS    the class name of the java class.\n");
@@ -1260,10 +1011,13 @@
     string headerFilename;
     string javaFilename;
     string jniFilename;
+    string atomsInfoCppFilename;
+    string atomsInfoHeaderFilename;
 
     string moduleName = DEFAULT_MODULE_NAME;
     string cppNamespace = DEFAULT_CPP_NAMESPACE;
     string cppHeaderImport = DEFAULT_CPP_HEADER_IMPORT;
+    string atomsInfoCppHeaderImport = DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT;
     string javaPackage = DEFAULT_JAVA_PACKAGE;
     string javaClass = DEFAULT_JAVA_CLASS;
 
@@ -1335,14 +1089,38 @@
                 return 1;
             }
             javaClass = argv[index];
+        } else if (0 == strcmp("--atomsInfoHeader", argv[index])) {
+            index++;
+            if (index >= argc) {
+                print_usage();
+                return 1;
+            }
+            atomsInfoHeaderFilename = argv[index];
+        } else if (0 == strcmp("--atomsInfoCpp", argv[index])) {
+            index++;
+            if (index >= argc) {
+                print_usage();
+                return 1;
+            }
+            atomsInfoCppFilename = argv[index];
+        } else if (0 == strcmp("--atomsInfoImportHeader", argv[index])) {
+            index++;
+            if (index >= argc) {
+                print_usage();
+                return 1;
+            }
+            atomsInfoCppHeaderImport = argv[index];
         }
+
         index++;
     }
 
     if (cppFilename.size() == 0
             && headerFilename.size() == 0
             && javaFilename.size() == 0
-            && jniFilename.size() == 0) {
+            && jniFilename.size() == 0
+            && atomsInfoHeaderFilename.size() == 0
+            && atomsInfoCppFilename.size() == 0) {
         print_usage();
         return 1;
     }
@@ -1359,6 +1137,30 @@
     collate_atom(android::os::statsd::AttributionNode::descriptor(),
                  &attributionDecl, &attributionSignature);
 
+    // Write the atoms info .cpp file
+    if (atomsInfoCppFilename.size() != 0) {
+        FILE* out = fopen(atomsInfoCppFilename.c_str(), "w");
+        if (out == NULL) {
+            fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoCppFilename.c_str());
+            return 1;
+        }
+        errorCount = android::stats_log_api_gen::write_atoms_info_cpp(
+            out, atoms, cppNamespace, atomsInfoCppHeaderImport, cppHeaderImport);
+        fclose(out);
+    }
+
+    // Write the atoms info .h file
+    if (atomsInfoHeaderFilename.size() != 0) {
+        FILE* out = fopen(atomsInfoHeaderFilename.c_str(), "w");
+        if (out == NULL) {
+            fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoHeaderFilename.c_str());
+            return 1;
+        }
+        errorCount = android::stats_log_api_gen::write_atoms_info_header(out, atoms, cppNamespace);
+        fclose(out);
+    }
+
+
     // Write the .cpp file
     if (cppFilename.size() != 0) {
         FILE* out = fopen(cppFilename.c_str(), "w");
diff --git a/tools/stats_log_api_gen/utils.cpp b/tools/stats_log_api_gen/utils.cpp
index 141861d..d6cfe95 100644
--- a/tools/stats_log_api_gen/utils.cpp
+++ b/tools/stats_log_api_gen/utils.cpp
@@ -16,9 +16,19 @@
 
 #include "utils.h"
 
+#include "android-base/strings.h"
+
 namespace android {
 namespace stats_log_api_gen {
 
+static void build_non_chained_decl_map(const Atoms& atoms,
+                                std::map<int, set<AtomDecl>::const_iterator>* decl_map) {
+    for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
+        atom != atoms.non_chained_decls.end(); atom++) {
+        decl_map->insert(std::make_pair(atom->code, atom));
+    }
+}
+
 /**
  * Turn lower and camel case into upper case with underscores.
  */
@@ -102,14 +112,98 @@
     return modules.find(moduleName) != modules.end();
 }
 
-void build_non_chained_decl_map(const Atoms& atoms,
-                                std::map<int, set<AtomDecl>::const_iterator>* decl_map) {
-    for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
-        atom != atoms.non_chained_decls.end(); atom++) {
-        decl_map->insert(std::make_pair(atom->code, atom));
+// Native
+// Writes namespaces for the cpp and header files, returning the number of namespaces written.
+void write_namespace(FILE* out, const string& cppNamespaces) {
+    vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
+    for (string cppNamespace : cppNamespaceVec) {
+        fprintf(out, "namespace %s {\n", cppNamespace.c_str());
     }
 }
 
+// Writes namespace closing brackets for cpp and header files.
+void write_closing_namespace(FILE* out, const string& cppNamespaces) {
+    vector<string> cppNamespaceVec = android::base::Split(cppNamespaces, ",");
+    for (auto it = cppNamespaceVec.rbegin(); it != cppNamespaceVec.rend(); ++it) {
+        fprintf(out, "} // namespace %s\n", it->c_str());
+    }
+}
+
+static void write_cpp_usage(
+    FILE* out, const string& method_name, const string& atom_code_name,
+    const AtomDecl& atom, const AtomDecl &attributionDecl) {
+    fprintf(out, "     * Usage: %s(StatsLog.%s", method_name.c_str(),
+            atom_code_name.c_str());
+
+    for (vector<AtomField>::const_iterator field = atom.fields.begin();
+            field != atom.fields.end(); field++) {
+        if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+            for (auto chainField : attributionDecl.fields) {
+                if (chainField.javaType == JAVA_TYPE_STRING) {
+                    fprintf(out, ", const std::vector<%s>& %s",
+                         cpp_type_name(chainField.javaType),
+                         chainField.name.c_str());
+                } else {
+                    fprintf(out, ", const %s* %s, size_t %s_length",
+                         cpp_type_name(chainField.javaType),
+                         chainField.name.c_str(), chainField.name.c_str());
+                }
+            }
+        } else if (field->javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
+            fprintf(out, ", const std::map<int, int32_t>& %s_int"
+                         ", const std::map<int, int64_t>& %s_long"
+                         ", const std::map<int, char const*>& %s_str"
+                         ", const std::map<int, float>& %s_float",
+                         field->name.c_str(),
+                         field->name.c_str(),
+                         field->name.c_str(),
+                         field->name.c_str());
+        } else {
+            fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+        }
+    }
+    fprintf(out, ");\n");
+}
+
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+        const string& moduleName) {
+    fprintf(out, "/**\n");
+    fprintf(out, " * Constants for atom codes.\n");
+    fprintf(out, " */\n");
+    fprintf(out, "enum {\n");
+
+    std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+    build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
+    size_t i = 0;
+    // Print atom constants
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+        atom != atoms.decls.end(); atom++) {
+        // Skip if the atom is not needed for the module.
+        if (!atom_needed_for_module(*atom, moduleName)) {
+            continue;
+        }
+        string constant = make_constant_name(atom->name);
+        fprintf(out, "\n");
+        fprintf(out, "    /**\n");
+        fprintf(out, "     * %s %s\n", atom->message.c_str(), atom->name.c_str());
+        write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
+
+        auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+        if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+            write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
+                attributionDecl);
+        }
+        fprintf(out, "     */\n");
+        char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
+        fprintf(out, "    %s = %d%s\n", constant.c_str(), atom->code, comma);
+        i++;
+    }
+    fprintf(out, "\n");
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+}
+
 // Java
 void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName) {
     fprintf(out, "    // Constants for atom codes.\n");
diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h
index e860fa9..a89387f 100644
--- a/tools/stats_log_api_gen/utils.h
+++ b/tools/stats_log_api_gen/utils.h
@@ -33,6 +33,7 @@
 const string DEFAULT_MODULE_NAME = "DEFAULT";
 const string DEFAULT_CPP_NAMESPACE = "android,util";
 const string DEFAULT_CPP_HEADER_IMPORT = "statslog.h";
+const string DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT = "atoms_info.h";
 const string DEFAULT_JAVA_PACKAGE = "android.util";
 const string DEFAULT_JAVA_CLASS = "StatsLogInternal";
 
@@ -49,8 +50,14 @@
 
 bool signature_needed_for_module(const set<string>& modules, const string& moduleName);
 
-void build_non_chained_decl_map(const Atoms& atoms,
-                                std::map<int, set<AtomDecl>::const_iterator>* decl_map);
+// Common Native helpers
+void write_namespace(FILE* out, const string& cppNamespaces);
+
+void write_closing_namespace(FILE* out, const string& cppNamespaces);
+
+void write_native_atom_constants(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+        const string& moduleName
+);
 
 // Common Java helpers.
 void write_java_atom_codes(FILE* out, const Atoms& atoms, const string& moduleName);
diff --git a/wifi/Android.bp b/wifi/Android.bp
new file mode 100644
index 0000000..08115ec
--- /dev/null
+++ b/wifi/Android.bp
@@ -0,0 +1,87 @@
+// 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.
+
+
+filegroup {
+    name: "framework-wifi-updatable-sources",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.aidl",
+    ],
+    exclude_srcs: [
+        ":framework-wifi-non-updatable-sources"
+    ],
+    path: "java",
+}
+
+filegroup {
+    name: "framework-wifi-non-updatable-sources",
+    srcs: [
+        // TODO(b/146011398) package android.net.wifi is now split amongst 2 jars: framework.jar and
+        // 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/WifiCondManager.java",
+        "java/android/net/wifi/wificond/*.java",
+        ":libwificond_ipc_aidl",
+    ],
+}
+
+filegroup {
+    name: "framework-wifi-annotations",
+    srcs: ["java/android/net/wifi/WifiAnnotations.java"],
+}
+
+java_library {
+    name: "framework-wifi",
+    sdk_version: "core_platform", // TODO(b/140299412) should be core_current
+    libs: [
+        "framework-minus-apex", // TODO(b/140299412) should be framework-system-stubs
+    ],
+    srcs: [
+        ":framework-wifi-updatable-sources",
+    ],
+    installable: true,
+    optimize: {
+        enabled: false
+    }
+}
+
+droidstubs {
+    name: "framework-wifi-stubs-srcs",
+    srcs: [
+        ":framework-annotations",
+        ":framework-wifi-updatable-sources",
+    ],
+    aidl: {
+        include_dirs: ["frameworks/base/core/java"],
+    },
+    defaults: [ "framework-module-stubs-defaults-systemapi" ],
+    sdk_version: "core_current",
+    libs: ["android_system_stubs_current"],
+}
+
+java_library {
+    name: "framework-wifi-stubs",
+    srcs: [":framework-wifi-stubs-srcs"],
+    aidl: {
+        export_include_dirs: [
+            "java",
+        ],
+    },
+    sdk_version: "core_current",
+    libs: ["android_system_stubs_current"],
+    installable: false,
+}
+
diff --git a/wifi/java/android/net/wifi/WifiAnnotations.java b/wifi/java/android/net/wifi/WifiAnnotations.java
new file mode 100644
index 0000000..4a7dee1
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiAnnotations.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.annotation.IntDef;
+import android.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Wifi annotations meant to be statically linked into client modules, since they cannot be
+ * exposed as @SystemApi.
+ *
+ * e.g. {@link IntDef}, {@link StringDef}
+ *
+ * @hide
+ */
+public final class WifiAnnotations {
+    private WifiAnnotations() {}
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SCAN_TYPE_"}, value = {
+            WifiScanner.SCAN_TYPE_LOW_LATENCY,
+            WifiScanner.SCAN_TYPE_LOW_POWER,
+            WifiScanner.SCAN_TYPE_HIGH_ACCURACY})
+    public @interface ScanType {}
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"WIFI_BAND_"}, value = {
+            WifiScanner.WIFI_BAND_UNSPECIFIED,
+            WifiScanner.WIFI_BAND_24_GHZ,
+            WifiScanner.WIFI_BAND_5_GHZ,
+            WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY,
+            WifiScanner.WIFI_BAND_6_GHZ})
+    public @interface WifiBandBasic {}
+}
diff --git a/wifi/java/android/net/wifi/WifiCondManager.java b/wifi/java/android/net/wifi/WifiCondManager.java
index 9ae7e3a..c05ba34 100644
--- a/wifi/java/android/net/wifi/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/WifiCondManager.java
@@ -725,7 +725,7 @@
     /**
      * Return scan type for the parcelable {@link SingleScanSettings}
      */
-    private static int getScanType(@WifiScanner.ScanType int scanType) {
+    private static int getScanType(@WifiAnnotations.ScanType int scanType) {
         switch (scanType) {
             case WifiScanner.SCAN_TYPE_LOW_LATENCY:
                 return IWifiScannerImpl.SCAN_TYPE_LOW_SPAN;
@@ -746,7 +746,7 @@
      * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
      * @return Returns true on success.
      */
-    public boolean scan(@NonNull String ifaceName, @WifiScanner.ScanType int scanType,
+    public boolean scan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
             Set<Integer> freqs, List<byte[]> hiddenNetworkSSIDs) {
         IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
         if (scannerImpl == null) {
@@ -868,7 +868,7 @@
      * @return frequencies vector of valid frequencies (MHz), or null for error.
      * @throws IllegalArgumentException if band is not recognized.
      */
-    public int [] getChannelsForBand(@WifiScanner.WifiBandBasic int band) {
+    public int [] getChannelsForBand(@WifiAnnotations.WifiBandBasic int band) {
         if (mWificond == null) {
             Log.e(TAG, "No valid wificond scanner interface handler");
             return null;
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 760497b..8fedda4 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -89,16 +89,6 @@
     /** 6 GHz band */
     public static final int WIFI_BAND_6_GHZ = 1 << WIFI_BAND_INDEX_6_GHZ;
 
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"WIFI_BAND_"}, value = {
-            WIFI_BAND_UNSPECIFIED,
-            WIFI_BAND_24_GHZ,
-            WIFI_BAND_5_GHZ,
-            WIFI_BAND_5_GHZ_DFS_ONLY,
-            WIFI_BAND_6_GHZ})
-    public @interface WifiBandBasic {}
-
     /**
      * Combination of bands
      * Note that those are only the common band combinations,
@@ -249,14 +239,6 @@
      */
     public static final int REPORT_EVENT_NO_BATCH = (1 << 2);
 
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"SCAN_TYPE_"}, value = {
-            SCAN_TYPE_LOW_LATENCY,
-            SCAN_TYPE_LOW_POWER,
-            SCAN_TYPE_HIGH_ACCURACY})
-    public @interface ScanType {}
-
     /**
      * Optimize the scan for lower latency.
      * @see ScanSettings#type
@@ -354,7 +336,7 @@
          * {@link #SCAN_TYPE_HIGH_ACCURACY}.
          * Default value: {@link #SCAN_TYPE_LOW_LATENCY}.
          */
-        @ScanType
+        @WifiAnnotations.ScanType
         @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
         public int type = SCAN_TYPE_LOW_LATENCY;
         /**
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
index 3453d6e..d2c385b4 100644
--- a/wifi/tests/Android.mk
+++ b/wifi/tests/Android.mk
@@ -59,6 +59,8 @@
 
 LOCAL_PACKAGE_NAME := FrameworksWifiApiTests
 LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_COMPATIBILITY_SUITE := \
+    device-tests \
+    mts \
 
 include $(BUILD_PACKAGE)
diff --git a/wifi/tests/AndroidTest.xml b/wifi/tests/AndroidTest.xml
index cae19e4..987fee7 100644
--- a/wifi/tests/AndroidTest.xml
+++ b/wifi/tests/AndroidTest.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 <configuration description="Runs Frameworks Wifi API Tests.">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="FrameworksWifiApiTests.apk" />
     </target_preparer>
 
diff --git a/wifi/tests/runtests.sh b/wifi/tests/runtests.sh
index 7a0dfb0..4024371 100755
--- a/wifi/tests/runtests.sh
+++ b/wifi/tests/runtests.sh
@@ -16,7 +16,6 @@
 
 set -x # print commands
 
-adb root
 adb wait-for-device
 
 TARGET_ARCH=$($ANDROID_BUILD_TOP/build/soong/soong_ui.bash --dumpvar-mode TARGET_ARCH)