Merge "Notification might not have a contentIntent"
diff --git a/Android.bp b/Android.bp
index 2d7e2d4..3b6eaa7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -194,13 +194,14 @@
         ":framework-core-sources",
         ":framework-drm-sources",
         ":framework-graphics-sources",
+        ":framework-jobscheduler-sources", // jobscheduler is not a module for R
         ":framework-keystore-sources",
         ":framework-location-sources",
         ":framework-lowpan-sources",
-        ":framework-media-sources",
         ":framework-mca-effect-sources",
         ":framework-mca-filterfw-sources",
         ":framework-mca-filterpacks-sources",
+        ":framework-media-sources",
         ":framework-mime-sources",
         ":framework-mms-sources",
         ":framework-opengl-sources",
@@ -213,6 +214,9 @@
         ":PacProcessor-aidl-sources",
         ":ProxyHandler-aidl-sources",
 
+        // AIDL from frameworks/base/native/
+        ":platform-compat-native-aidl",
+
         // AIDL sources from external directories
         ":dumpstate_aidl",
         ":framework_native_aidl",
@@ -407,7 +411,6 @@
     installable: true,
     static_libs: [
         "framework-minus-apex",
-        "jobscheduler-framework",
     ],
     required: [
         "framework-platform-compat-config",
@@ -962,7 +965,6 @@
         ":updatable-media-srcs",
         "test-mock/src/**/*.java",
         "test-runner/src/**/*.java",
-        ":jobscheduler-framework-source",
     ],
     libs: framework_docs_only_libs,
     local_sourcepaths: frameworks_base_subdirs,
@@ -1025,7 +1027,6 @@
         ":core-current-stubs-source",
         ":core_public_api_files",
         ":updatable-media-srcs",
-        ":jobscheduler-framework-source",
     ],
     libs: ["framework-internal-utils"],
     local_sourcepaths: frameworks_base_subdirs,
diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index 621ff9a..3902aa2 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -1,5 +1,5 @@
 filegroup {
-    name: "jobscheduler-framework-source",
+    name: "framework-jobscheduler-sources",
     srcs: [
         "java/**/*.java",
         "java/android/app/job/IJobCallback.aidl",
@@ -12,13 +12,12 @@
 
 java_library {
     name: "jobscheduler-framework",
-    installable: true,
+    installable: false,
+    compile_dex: true,
     sdk_version: "core_platform",
-
     srcs: [
-        ":jobscheduler-framework-source",
+        ":framework-jobscheduler-sources",
     ],
-
     aidl: {
         export_include_dirs: [
             "java",
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
new file mode 100644
index 0000000..0274814
--- /dev/null
+++ b/apex/permission/Android.bp
@@ -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.
+
+apex {
+    name: "com.android.permission",
+
+    manifest: "apex_manifest.json",
+
+    key: "com.android.permission.key",
+    certificate: ":com.android.permission.certificate",
+}
+
+apex_key {
+    name: "com.android.permission.key",
+    public_key: "com.android.permission.avbpubkey",
+    private_key: "com.android.permission.pem",
+}
+
+android_app_certificate {
+    name: "com.android.permission.certificate",
+    certificate: "com.android.permission",
+}
diff --git a/apex/permission/OWNERS b/apex/permission/OWNERS
new file mode 100644
index 0000000..957e10a
--- /dev/null
+++ b/apex/permission/OWNERS
@@ -0,0 +1,6 @@
+svetoslavganov@google.com
+moltmann@google.com
+eugenesusla@google.com
+zhanghai@google.com
+evanseverson@google.com
+ntmyren@google.com
diff --git a/apex/permission/apex_manifest.json b/apex/permission/apex_manifest.json
new file mode 100644
index 0000000..2a8c4f7
--- /dev/null
+++ b/apex/permission/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.permission",
+  "version": 1
+}
diff --git a/apex/permission/com.android.permission.avbpubkey b/apex/permission/com.android.permission.avbpubkey
new file mode 100644
index 0000000..9eaf852
--- /dev/null
+++ b/apex/permission/com.android.permission.avbpubkey
Binary files differ
diff --git a/apex/permission/com.android.permission.pem b/apex/permission/com.android.permission.pem
new file mode 100644
index 0000000..3d584be
--- /dev/null
+++ b/apex/permission/com.android.permission.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKgIBAAKCAgEA6snt4eqoz85xiL9Sf6w1S1b9FgSHK05zYTh2JYPvQKQ3yeZp
+E6avJ6FN6XcbmkDzSd658BvUGDBSPhOlzuUO4BsoKBuLMxP6TxIQXFKidzDqY0vQ
+4qkS++bdIhUjwBP3OSZ3Czu0BiihK8GC75Abr//EyCyObGIGGfHEGANiOgrpP4X5
++OmLzQLCjk4iE1kg+U6cRSRI/XLaoWC0TvIIuzxznrQ6r5GmzgTOwyBWyIB+bj73
+bmsweHTU+w9Y7kGOx4hO3XCLIhoBWEw0EbuW9nZmQ4sZls5Jo/CbyJlCclF11yVo
+SCf2LG/T+9pah5NOmDQ1kPbU+0iKZIV4YFHGTIhyGDE/aPOuUT05ziCGDifgHr0u
+SG1x/RLqsVh/POvNxnvP9cQFMQ08BvbEJaTTgB785iwKsvdqCfmng/SAyxSetmzP
+StXVB3fh1OoZ8vunRbQYxnmUxycVqaA96zmBx2wLvbvzKo7pZFDE6nbhnT5+MRAM
+z/VIK89W26uB4gj8sBFslqZjT0jPqsAZuvDm7swOtMwIcEolyGJuFLqlhN7UwMz2
+9y8+IpYixR+HvD1TZI9NtmuCmv3kPrWgoMZg6yvaBayTIr8RdYzi6FO/C1lLiraz
+48dH3sXWRa8cgw6VcSUwYrEBIc3sotdsupO1iOjcFybIwaee0YTZJfjvbqkCAwEA
+AQKCAgEArRnfdpaJi1xLPGTCMDsIt9kUku0XswgN7PmxsYsKFAB+2S40/jYAIRm9
+1YjpItsMA8RgFfSOdJ77o6TctCMQyo17F8bm4+uwuic5RLfv7Cx2QmsdQF8jDfFx
+y7UGPJD7znjbf76uxXOjEB2FqZX3s9TAgkzHXIUQtoQW7RVhkCWHPjxKxgd5+NY2
+FrDoUpd9xhD9CcTsw1+wbRZdGW88nL6/B50dP2AFORM2VYo8MWr6y9FEn3YLsGOC
+uu7fxBk1aUrHyl81VRkTMMROB1zkuiUk1FtzrEm+5U15rXXBFYOVe9+qeLhtuOlh
+wueDoz0pzvF/JLe24uTik6YL0Ae6SD0pFXQ2KDrdH3cUHLok3r76/yGzaDNTFjS2
+2WbQ8dEJV8veNHk8gjGpFTJIsBUlcZpmUCDHlfvVMb3+2ahQ+28piQUt5t3zqJdZ
+NDqsOHzY6BRPc+Wm85Xii/lWiQceZSee/b1Enu+nchsyXhSenBfC6bIGZReyMI0K
+KKKuVhyR6OSOiR5ZdZ/NyXGqsWy05fn/h0X9hnpETsNaNYNKWvpHLfKll+STJpf7
+AZquJPIclQyiq5NONx6kfPztoCLkKV/zOgIj3Sx5oSZq+5gpO91nXWVwkTbqK1d1
+004q2Mah6UQyAk1XGQc2pHx7ouVcWawjU30vZ4C015Hv2lm/gVkCggEBAPltATYS
+OqOSL1YAtIHPiHxMjNAgUdglq8JiJFXVfkocGU9eNub3Ed3sSWu6GB9Myu/sSKje
+bJ5DZqxJnvB2Fqmu9I9OunLGFSD0aXs4prwsQ1Rm5FcbImtrxcciASdkoo8Pj0z4
+vk2r2NZD3VtER5Uh+YjSDkxcS9gBStXUpCL6gj69UpOxMmWqZVjyHatVB4lEvYJl
+N82uT7N7QVNL1DzcZ9z4C4r7ks1Pm7ka12s5m/oaAlAMdVeofiPJe1xA9zRToSr4
+tIbMkOeXFLVRLuji/7XsOgal5Rl59p+OwLshX5cswPVOMrH6zt+hbsJ5q8M5dqnX
+VAOBK7KNQ/EKZwcCggEBAPD6KVvyCim46n5EbcEqCkO7gevwZkw/9vLwmM5YsxTh
+z9FQkPO0iB7mwbX8w04I91Pre4NdfcgMG0pP1b13Sb4KHBchqW1a+TCs3kSGC6gn
+1SxmXHnA9jRxAkrWlGkoAQEz+aP61cXiiy2tXpQwJ8xQCKprfoqWZwhkCtEVU6CE
+S7v9cscOHIqgNxx4WoceMmq4EoihHAZzHxTcNVbByckMjb2XQJ0iNw3lDP4ddvc+
+a4HzHfHkhzeQ5ZNc8SvWU8z80aSCOKRsSD3aUTZzxhZ4O2tZSW7v7p+FpvVee7bC
+g8YCfszTdpVUMlLRLjScimAcovcFLSvtyupinxWg4M8CggEAN9YGEmOsSte7zwXj
+YrfhtumwEBtcFwX/2Ej+F1Tuq4p0xAa0RaoDjumJWhtTsRYQy/raHSuFpzwxbNoi
+QXQ+CIhI6RfXtz/OlQ0B2/rHoJJMFEXgUfuaDfAXW0eqeHYXyezSyIlamKqipPyW
+Pgsf9yue39keKEv1EorfhNTQVaA8rezV4oglXwrxGyNALw2e3UTNI7ai8mFWKDis
+XAg6n9E7UwUYGGnO6DUtCBgRJ0jDOQ6/e8n+LrxiWIKPIgzNCiK6jpMUXqTGv4Fb
+umdNGAdQ9RnHt5tFmRlrczaSwJFtA7uaCpAR2zPpQbiywchZAiAIB2dTwGEXNiZX
+kksg2wKCAQEA6pNad3qhkgPDoK6T+Jkn7M82paoaqtcJWWwEE7oceZNnbWZz9Agl
+CY+vuawXonrv5+0vCq2Tp4zBdBFLC2h3jFrjBVFrUFxifpOIukOSTVqZFON/2bWQ
+9XOcu6UuSz7522Xw+UNPnZXtzcUacD6AP08ZYGvLfrTyDyTzspyED5k48ALEHCkM
+d5WGkFxII4etpF0TDZVnZo/iDbhe49k4yFFEGO6Ho26PESOLBkNAb2V/2bwDxlij
+l9+g21Z6HiZA5SamHPH2mXgeyrcen1cL2QupK9J6vVcqfnboE6qp2zp2c+Yx8MlY
+gfy4EA44YFaSDQVTTgrn8f9Eq+zc130H2QKCAQEAqOKgv68nIPdDSngNyCVyWego
+boFiDaEJoBBg8FrBjTJ6wFLrNAnXmbvfTtgNmNAzF1cUPJZlIIsHgGrMCfpehbXq
+WQQIw+E+yFbTGLxseGRfsLrV0CsgnAoOVeod+yIHmqc3livaUbrWhL1V2f6Ue+sE
+7YLp/iP43NaMfA4kYk2ep7+ZJoEVkCjHJJaHWgAG3RynPJHkTJlSgu7wLYvGc9uE
+ZsEFUM46lX02t7rrtMfasVGrUy1c2xOxFb4v1vG6iEZ7+YWeq5o3AkxUwEGn+mG4
+/3p+k4AaTXJDXgyZ0Sv6CkGuPHenAYG4cswcUUEf/G4Ag77x6LBNMgycJBxUJA==
+-----END RSA PRIVATE KEY-----
diff --git a/apex/permission/com.android.permission.pk8 b/apex/permission/com.android.permission.pk8
new file mode 100644
index 0000000..d51673d
--- /dev/null
+++ b/apex/permission/com.android.permission.pk8
Binary files differ
diff --git a/apex/permission/com.android.permission.x509.pem b/apex/permission/com.android.permission.x509.pem
new file mode 100644
index 0000000..4b146c9
--- /dev/null
+++ b/apex/permission/com.android.permission.x509.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGKzCCBBOgAwIBAgIUezo3fQeVZsmLpm/dkpGWJ/G/MN8wDQYJKoZIhvcNAQEL
+BQAwgaMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMR8wHQYDVQQDDBZjb20uYW5kcm9pZC5wZXJtaXNzaW9uMSIwIAYJKoZIhvcN
+AQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMCAXDTE5MTAwOTIxMzExOVoYDzQ3NTcw
+OTA0MjEzMTE5WjCBozELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx
+FjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB0FuZHJvaWQxEDAOBgNV
+BAsMB0FuZHJvaWQxHzAdBgNVBAMMFmNvbS5hbmRyb2lkLnBlcm1pc3Npb24xIjAg
+BgkqhkiG9w0BCQEWE2FuZHJvaWRAYW5kcm9pZC5jb20wggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCxefguRJ7E6tBCTEOeU2HJEGs6AQQapLz9hMed0aaJ
+Qr7aTQiYJEk+sG4+jPYbjpxa8JDDzJHp+4g7DjfSb+dvT9n84A8lWaI/yRXTZTQN
+Hu5m/bgHhi0LbySpiaFyodXBKUAnOhZyGPtYjtBFywFylueub8ryc1Z6UxxU7udH
+1mkIr7sE48Qkq5SyjFROE96iFmYA+vS/JXOfS0NBHiMB4GBxx4V7kXpvrTI7hhZG
+HiyhKvNh7wyHIhO9nDEw1rwtAH6CsL3YkQEVBeAU98m+0Au+qStLYkKHh2l8zT4W
+7sVK1VSqfB+VqOUmeIGdzlBfqMsoXD+FJz6KnIdUHIwjFDjL7Xr+hd+7xve+Q3S+
+U3Blk/U6atY8PM09wNfilG+SvwcKk5IgriDcu3rWKgIFxbUUaxLrDW7pLlu6wt/d
+GGtKK+Bc0jF+9Z901Tl33i5xhc5yOktT0btkKs7lSeE6VzP/Nk5g0SuzixmuRoh9
+f5Ge41N2ZCEHNXx3wZeVZwHIIPfYrL7Yql1Xoxbfs4ETFk6ChzVQcvjfDQQuK58J
+uNc+TOCoI/qflXwGCwpuHl0ier8V5Z4tpMUl5rWyVR/QGRtLPvs2lLuxczDw1OXq
+wEVtCMn9aNnd4y7R9PZ52hi53HAvDjpWefrLYi+Q04J6iGFQ1qAFBClK9DquBvmR
+swIDAQABo1MwUTAdBgNVHQ4EFgQULpfus5s5SrqLkoUKyPXA0D1iHPMwHwYDVR0j
+BBgwFoAULpfus5s5SrqLkoUKyPXA0D1iHPMwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAgEAjxQG5EFv8V/9yV2glI53VOmlWMjfEgvUjd39s/XLyPlr
+OzPOKSB0NFo8To3l4l+MsManxPK8y0OyfEVKbWVz9onv0ovo5MVokBmV/2G0jmsV
+B4e9yjOq+DmqIvY/Qh63Ywb97sTgcFI8620MhQDbh2IpEGv4ZNV0H6rgXmgdSCBw
+1EjBoYfFpN5aMgZjeyzZcq+d1IapdWqdhuEJQkMvoYS4WIumNIJlEXPQRoq/F5Ih
+nszdbKI/jVyiGFa2oeZ3rja1Y6GCRU8TYEoKx1pjS8uQDOEDTwsG/QnUe9peEj0V
+SsCkIidJWTomAmq9Tub9vpBe1zuTpuRAwxwR0qwgSxozV1Mvow1dJ19oFtHX0yD6
+ZjCpRn5PW9kMvSWSlrcrFs1NJf0j1Cvf7bHpkEDqLqpMnnh9jaFQq3nzDY+MWcIR
+jDcgQpI+AiE2/qtauZnFEVhbce49nCnk9+5bpTTIZJdzqeaExe5KXHwEtZLaEDh4
+atLY9LuEvPsjmDIMOR6hycD9FvwGXhJOQBjESIWFwigtSb1Yud9n6201jw3MLJ4k
++WhkbmZgWy+xc+Mdm5H3XyB1lvHaHGkxu+QB9KyQuVQKwbUVcbwZIfTFPN6Zr/dS
+ZXJqAbBhG/dBgF0LazuLaPVpibi+a3Y+tb9b8eXGkz4F97PWZIEDkELQ+9KOvhc=
+-----END CERTIFICATE-----
diff --git a/api/current.txt b/api/current.txt
index 71dc58b..0a317c3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6851,6 +6851,7 @@
     method public boolean setKeyPairCertificate(@Nullable android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.security.cert.Certificate>, boolean);
     method public boolean setKeyguardDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(@NonNull android.content.ComponentName, int);
+    method public void setLocationEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setLockTaskFeatures(@NonNull android.content.ComponentName, int);
     method public void setLockTaskPackages(@NonNull android.content.ComponentName, @NonNull String[]) throws java.lang.SecurityException;
     method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean);
@@ -29975,10 +29976,16 @@
     method public String getSSID();
     method public android.net.wifi.SupplicantState getSupplicantState();
     method @IntRange(from=0xffffffff) public int getTxLinkSpeedMbps();
+    method public int getWifiTechnology();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final String FREQUENCY_UNITS = "MHz";
     field public static final String LINK_SPEED_UNITS = "Mbps";
     field public static final int LINK_SPEED_UNKNOWN = -1; // 0xffffffff
+    field public static final int WIFI_TECHNOLOGY_11AC = 5; // 0x5
+    field public static final int WIFI_TECHNOLOGY_11AX = 6; // 0x6
+    field public static final int WIFI_TECHNOLOGY_11N = 4; // 0x4
+    field public static final int WIFI_TECHNOLOGY_LEGACY = 1; // 0x1
+    field public static final int WIFI_TECHNOLOGY_UNKNOWN = 0; // 0x0
   }
 
   public class WifiManager {
@@ -30336,6 +30343,8 @@
     method public int describeContents();
     method public android.net.wifi.hotspot2.pps.Credential getCredential();
     method public android.net.wifi.hotspot2.pps.HomeSp getHomeSp();
+    method public long getSubscriptionExpirationTimeInMillis();
+    method public boolean isOsuProvisioned();
     method public void setCredential(android.net.wifi.hotspot2.pps.Credential);
     method public void setHomeSp(android.net.wifi.hotspot2.pps.HomeSp);
     method public void writeToParcel(android.os.Parcel, int);
@@ -41368,11 +41377,16 @@
     field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
     field public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; // 0x0
     field public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; // 0x1
+    field public static final int POSITIVE_BUTTON_STYLE_CONTINUE = 1; // 0x1
+    field public static final int POSITIVE_BUTTON_STYLE_SAVE = 0; // 0x0
     field public static final int SAVE_DATA_TYPE_ADDRESS = 2; // 0x2
     field public static final int SAVE_DATA_TYPE_CREDIT_CARD = 4; // 0x4
+    field public static final int SAVE_DATA_TYPE_DEBIT_CARD = 32; // 0x20
     field public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 16; // 0x10
     field public static final int SAVE_DATA_TYPE_GENERIC = 0; // 0x0
+    field public static final int SAVE_DATA_TYPE_GENERIC_CARD = 128; // 0x80
     field public static final int SAVE_DATA_TYPE_PASSWORD = 1; // 0x1
+    field public static final int SAVE_DATA_TYPE_PAYMENT_CARD = 64; // 0x40
     field public static final int SAVE_DATA_TYPE_USERNAME = 8; // 0x8
   }
 
@@ -41386,6 +41400,7 @@
     method @NonNull public android.service.autofill.SaveInfo.Builder setFlags(int);
     method @NonNull public android.service.autofill.SaveInfo.Builder setNegativeAction(int, @Nullable android.content.IntentSender);
     method @NonNull public android.service.autofill.SaveInfo.Builder setOptionalIds(@NonNull android.view.autofill.AutofillId[]);
+    method @NonNull public android.service.autofill.SaveInfo.Builder setPositiveAction(int);
     method @NonNull public android.service.autofill.SaveInfo.Builder setTriggerId(@NonNull android.view.autofill.AutofillId);
     method @NonNull public android.service.autofill.SaveInfo.Builder setValidator(@NonNull android.service.autofill.Validator);
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 295fef1..35b096d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4780,8 +4780,11 @@
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeInitiator(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource);
+    method @RequiresPermission(anyOf={"android.permission.NETWORK_STACK", android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean startSoftAp(@Nullable android.net.wifi.WifiConfiguration);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(@NonNull android.net.wifi.hotspot2.OsuProvider, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.hotspot2.ProvisioningCallback);
     method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
+    method @RequiresPermission(anyOf={"android.permission.NETWORK_STACK", android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean stopSoftAp();
+    method @RequiresPermission(anyOf={"android.permission.NETWORK_STACK", android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateInterfaceIpState(@Nullable String, int);
     method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int);
     field public static final int CHANGE_REASON_ADDED = 0; // 0x0
     field public static final int CHANGE_REASON_CONFIG_CHANGE = 2; // 0x2
@@ -4796,10 +4799,16 @@
     field public static final String EXTRA_CHANGE_REASON = "changeReason";
     field public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
     field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
+    field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
+    field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
     field public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
     field public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
     field public static final String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et";
     field public static final String EXTRA_WIFI_CREDENTIAL_SSID = "ssid";
+    field public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0; // 0x0
+    field public static final int IFACE_IP_MODE_LOCAL_ONLY = 2; // 0x2
+    field public static final int IFACE_IP_MODE_TETHERED = 1; // 0x1
+    field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff
     field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0
     field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1
     field public static final String WIFI_AP_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_AP_STATE_CHANGED";
@@ -5636,6 +5645,8 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile(@NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean removeUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
@@ -8285,6 +8296,9 @@
 
   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);
diff --git a/api/test-current.txt b/api/test-current.txt
index e376c47..b3834bf 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3133,7 +3133,6 @@
     field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
     field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer";
     field public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2";
-    field public static final String USE_BUGREPORT_API = "settings_use_bugreport_api";
   }
 
   public class TimeUtils {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 26c2c0c..f4e465a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -563,6 +563,21 @@
      * account, or the AbstractAcccountAuthenticator managing the account did so or because the
      * client shares a signature with the managing AbstractAccountAuthenticator.
      *
+     * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+     * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+     * disclose that fact to users. For apps published on Google Play, policies protecting user data
+     * require that you do the following:</p>
+     * <ul>
+     * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+     * sensitive data. Learn more about
+     * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+     * disclosure and consent</a>.</li>
+     * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+     * </ul>
+     * <p>To learn more, visit the
+     * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+     * Policy regarding user data</a>.</p></div>
+     *
      * <p>
      * It is safe to call this method from the main thread.
      *
@@ -649,6 +664,22 @@
      * the account. For example, there are types corresponding to Google and Facebook. The exact
      * string token to use will be published somewhere associated with the authenticator in
      * question.
+     * </p>
+     *
+     * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+     * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+     * disclose that fact to users. For apps published on Google Play, policies protecting user data
+     * require that you do the following:</p>
+     * <ul>
+     * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+     * sensitive data. Learn more about
+     * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+     * disclosure and consent</a>.</li>
+     * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+     * </ul>
+     * <p>To learn more, visit the
+     * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+     * Policy regarding user data</a>.</p></div>
      *
      * <p>
      * It is safe to call this method from the main thread.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 375a06e..ad671df 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8351,6 +8351,24 @@
     }
 
     /**
+     * Called by device owners to set the user's master location setting.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+     * @param locationEnabled whether location should be enabled or disabled
+     * @throws SecurityException if {@code admin} is not a device owner.
+     */
+    public void setLocationEnabled(@NonNull ComponentName admin, boolean locationEnabled) {
+        throwIfParentInstance("setLocationEnabled");
+        if (mService != null) {
+            try {
+                mService.setLocationEnabled(admin, locationEnabled);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Called by profile or device owners to update {@link android.provider.Settings.Secure}
      * settings. Validation that the value of the setting is in the correct form for the setting
      * type should be performed by the caller.
@@ -8379,6 +8397,11 @@
      * all users.
      * </strong>
      *
+     * <strong>Note: Starting from Android R, apps should no longer call this method with the
+     * setting {@link android.provider.Settings.Secure#LOCATION_MODE}, which is deprecated. Instead,
+     * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}.
+     * </strong>
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param setting The name of the setting to update.
      * @param value The value to update the setting to.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7d2c54e..6b50522 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -258,6 +258,8 @@
     void setSystemSetting(in ComponentName who, in String setting, in String value);
     void setSecureSetting(in ComponentName who, in String setting, in String value);
 
+    void setLocationEnabled(in ComponentName who, boolean locationEnabled);
+
     boolean setTime(in ComponentName who, long millis);
     boolean setTimeZone(in ComponentName who, String timeZone);
 
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index d9bfde5..0ecfca7 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -73,6 +73,11 @@
     // consider it a complex PIN/password.
     public static final int MAX_ALLOWED_SEQUENCE = 3;
 
+    // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN or CREDENTIAL_TYPE_PASSWORD.
+    // Note that this class still uses CREDENTIAL_TYPE_PASSWORD to represent both numeric PIN
+    // and alphabetic password. This is OK as long as this definition is only used internally,
+    // and the value never gets mixed up with credential types from other parts of the framework.
+    // TODO: fix this (ideally after we move logic to PasswordPolicy)
     public @CredentialType int credType;
     // Fields below only make sense when credType is PASSWORD.
     public int length = 0;
@@ -161,24 +166,26 @@
 
     public static final @NonNull Parcelable.Creator<PasswordMetrics> CREATOR
             = new Parcelable.Creator<PasswordMetrics>() {
-        public PasswordMetrics createFromParcel(Parcel in) {
-            int credType = in.readInt();
-            int length = in.readInt();
-            int letters = in.readInt();
-            int upperCase = in.readInt();
-            int lowerCase = in.readInt();
-            int numeric = in.readInt();
-            int symbols = in.readInt();
-            int nonLetter = in.readInt();
-            int nonNumeric = in.readInt();
-            int seqLength = in.readInt();
-            return new PasswordMetrics(credType, length, letters, upperCase, lowerCase, numeric,
-                    symbols, nonLetter, nonNumeric, seqLength);
-        }
+                @Override
+                public PasswordMetrics createFromParcel(Parcel in) {
+                    int credType = in.readInt();
+                    int length = in.readInt();
+                    int letters = in.readInt();
+                    int upperCase = in.readInt();
+                    int lowerCase = in.readInt();
+                    int numeric = in.readInt();
+                    int symbols = in.readInt();
+                    int nonLetter = in.readInt();
+                    int nonNumeric = in.readInt();
+                    int seqLength = in.readInt();
+                    return new PasswordMetrics(credType, length, letters, upperCase, lowerCase,
+                            numeric, symbols, nonLetter, nonNumeric, seqLength);
+                }
 
-        public PasswordMetrics[] newArray(int size) {
-            return new PasswordMetrics[size];
-        }
+                @Override
+                public PasswordMetrics[] newArray(int size) {
+                    return new PasswordMetrics[size];
+                }
     };
 
     /**
@@ -189,7 +196,7 @@
      * {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
      */
     public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
-        if (credential.isPassword()) {
+        if (credential.isPassword() || credential.isPin()) {
             return PasswordMetrics.computeForPassword(credential.getCredential());
         } else if (credential.isPattern())  {
             return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index f624446..ddc4932 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -16,10 +16,10 @@
 
 package android.app.timedetector;
 
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
 
 /**
- * System private API to comunicate with time detector service.
+ * System private API to communicate with time detector service.
  *
  * <p>Used by parts of the Android system with signals associated with the device's time to provide
  * information to the Time Detector Service.
@@ -32,5 +32,5 @@
  * {@hide}
  */
 interface ITimeDetectorService {
-  void suggestTime(in TimeSignal timeSignal);
+  void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion);
 }
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl
similarity index 94%
rename from core/java/android/app/timedetector/TimeSignal.aidl
rename to core/java/android/app/timedetector/PhoneTimeSuggestion.aidl
index d2ec357..f5e2405 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.aidl
@@ -16,4 +16,4 @@
 
 package android.app.timedetector;
 
-parcelable TimeSignal;
\ No newline at end of file
+parcelable PhoneTimeSuggestion;
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
new file mode 100644
index 0000000..475a4aa
--- /dev/null
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.TimestampedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A time signal from a telephony source. The value consists of the number of milliseconds elapsed
+ * since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number
+ * was established. The elapsed realtime clock is considered accurate but volatile, so time signals
+ * must not be persisted across device resets.
+ *
+ * @hide
+ */
+public final class PhoneTimeSuggestion implements Parcelable {
+
+    public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR =
+            new Parcelable.Creator<PhoneTimeSuggestion>() {
+                public PhoneTimeSuggestion createFromParcel(Parcel in) {
+                    return PhoneTimeSuggestion.createFromParcel(in);
+                }
+
+                public PhoneTimeSuggestion[] newArray(int size) {
+                    return new PhoneTimeSuggestion[size];
+                }
+            };
+
+    private final int mPhoneId;
+    @NonNull
+    private final TimestampedValue<Long> mUtcTime;
+    @Nullable
+    private ArrayList<String> mDebugInfo;
+
+    public PhoneTimeSuggestion(int phoneId, @NonNull TimestampedValue<Long> utcTime) {
+        mPhoneId = phoneId;
+        mUtcTime = Objects.requireNonNull(utcTime);
+    }
+
+    private static PhoneTimeSuggestion createFromParcel(Parcel in) {
+        int phoneId = in.readInt();
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        PhoneTimeSuggestion suggestion = new PhoneTimeSuggestion(phoneId, utcTime);
+        @SuppressWarnings("unchecked")
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        suggestion.mDebugInfo = debugInfo;
+        return suggestion;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mPhoneId);
+        dest.writeParcelable(mUtcTime, 0);
+        dest.writeList(mDebugInfo);
+    }
+
+    public int getPhoneId() {
+        return mPhoneId;
+    }
+
+    @NonNull
+    public TimestampedValue<Long> getUtcTime() {
+        return mUtcTime;
+    }
+
+    @NonNull
+    public List<String> getDebugInfo() {
+        return Collections.unmodifiableList(mDebugInfo);
+    }
+
+    /**
+     * Associates information with the instance that can be useful for debugging / logging. The
+     * information is present in {@link #toString()} but is not considered for
+     * {@link #equals(Object)} and {@link #hashCode()}.
+     */
+    public void addDebugInfo(String... debugInfos) {
+        if (mDebugInfo == null) {
+            mDebugInfo = new ArrayList<>();
+        }
+        mDebugInfo.addAll(Arrays.asList(debugInfos));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        PhoneTimeSuggestion that = (PhoneTimeSuggestion) o;
+        return mPhoneId == that.mPhoneId
+                && Objects.equals(mUtcTime, that.mUtcTime);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPhoneId, mUtcTime);
+    }
+
+    @Override
+    public String toString() {
+        return "PhoneTimeSuggestion{"
+                + "mPhoneId='" + mPhoneId + '\''
+                + ", mUtcTime=" + mUtcTime
+                + ", mDebugInfo=" + mDebugInfo
+                + '}';
+    }
+}
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 052050d..334e958 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -45,12 +45,12 @@
      * signals are available such as those that come from more reliable sources or were
      * determined more recently.
      */
-    public void suggestTime(@NonNull TimeSignal timeSignal) {
+    public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
         if (DEBUG) {
-            Log.d(TAG, "suggestTime called: " + timeSignal);
+            Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion);
         }
         try {
-            mITimeDetectorService.suggestTime(timeSignal);
+            mITimeDetectorService.suggestPhoneTime(timeSuggestion);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/timedetector/TimeSignal.java b/core/java/android/app/timedetector/TimeSignal.java
deleted file mode 100644
index b494260..0000000
--- a/core/java/android/app/timedetector/TimeSignal.java
+++ /dev/null
@@ -1,109 +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.app.timedetector;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.TimestampedValue;
-
-import java.util.Objects;
-
-/**
- * A time signal from a named source. The value consists of the number of milliseconds elapsed since
- * 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number was
- * established. The elapsed realtime clock is considered accurate but volatile, so time signals
- * must not be persisted across device resets.
- *
- * @hide
- */
-public final class TimeSignal implements Parcelable {
-
-    public static final @android.annotation.NonNull Parcelable.Creator<TimeSignal> CREATOR =
-            new Parcelable.Creator<TimeSignal>() {
-                public TimeSignal createFromParcel(Parcel in) {
-                    return TimeSignal.createFromParcel(in);
-                }
-
-                public TimeSignal[] newArray(int size) {
-                    return new TimeSignal[size];
-                }
-            };
-
-    public static final String SOURCE_ID_NITZ = "nitz";
-
-    private final String mSourceId;
-    private final TimestampedValue<Long> mUtcTime;
-
-    public TimeSignal(String sourceId, TimestampedValue<Long> utcTime) {
-        mSourceId = Objects.requireNonNull(sourceId);
-        mUtcTime = Objects.requireNonNull(utcTime);
-    }
-
-    private static TimeSignal createFromParcel(Parcel in) {
-        String sourceId = in.readString();
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
-        return new TimeSignal(sourceId, utcTime);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString(mSourceId);
-        dest.writeParcelable(mUtcTime, 0);
-    }
-
-    @NonNull
-    public String getSourceId() {
-        return mSourceId;
-    }
-
-    @NonNull
-    public TimestampedValue<Long> getUtcTime() {
-        return mUtcTime;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        TimeSignal that = (TimeSignal) o;
-        return Objects.equals(mSourceId, that.mSourceId)
-                && Objects.equals(mUtcTime, that.mUtcTime);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mSourceId, mUtcTime);
-    }
-
-    @Override
-    public String toString() {
-        return "TimeSignal{"
-                + "mSourceId='" + mSourceId + '\''
-                + ", mUtcTime=" + mUtcTime
-                + '}';
-    }
-}
diff --git a/core/java/android/app/usage/EventList.java b/core/java/android/app/usage/EventList.java
index 8c03405..afdcbe6 100644
--- a/core/java/android/app/usage/EventList.java
+++ b/core/java/android/app/usage/EventList.java
@@ -79,6 +79,21 @@
     }
 
     /**
+     * Removes the event at the given index.
+     *
+     * @param index the index of the event to remove
+     * @return the event removed, or {@code null} if the index was out of bounds
+     */
+    public UsageEvents.Event remove(int index) {
+        try {
+            return mEvents.remove(index);
+        } catch (IndexOutOfBoundsException e) {
+            // catch and handle the exception here instead of throwing it to the client
+            return null;
+        }
+    }
+
+    /**
      * Finds the index of the first event whose timestamp is greater than or equal to the given
      * timestamp.
      *
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 5dbca12..4bf9c04 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -312,6 +312,11 @@
         public static final int VALID_FLAG_BITS = FLAG_IS_PACKAGE_INSTANT_APP;
 
         /**
+         * @hide
+         */
+        private static final int UNASSIGNED_TOKEN = -1;
+
+        /**
          * {@hide}
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -320,12 +325,22 @@
         /**
          * {@hide}
          */
+        public int mPackageToken = UNASSIGNED_TOKEN;
+
+        /**
+         * {@hide}
+         */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public String mClass;
 
         /**
          * {@hide}
          */
+        public int mClassToken = UNASSIGNED_TOKEN;
+
+        /**
+         * {@hide}
+         */
         public int mInstanceId;
 
         /**
@@ -336,11 +351,21 @@
         /**
          * {@hide}
          */
+        public int mTaskRootPackageToken = UNASSIGNED_TOKEN;
+
+        /**
+         * {@hide}
+         */
         public String mTaskRootClass;
 
         /**
          * {@hide}
          */
+        public int mTaskRootClassToken = UNASSIGNED_TOKEN;
+
+        /**
+         * {@hide}
+         */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         public long mTimeStamp;
 
@@ -365,6 +390,11 @@
         public String mShortcutId;
 
         /**
+         * {@hide}
+         */
+        public int mShortcutIdToken = UNASSIGNED_TOKEN;
+
+        /**
          * Action type passed to ChooserActivity
          * Only present for {@link #CHOOSER_ACTION} event types.
          * {@hide}
@@ -401,6 +431,11 @@
          */
         public String mNotificationChannelId;
 
+        /**
+         * {@hide}
+         */
+        public int mNotificationChannelIdToken = UNASSIGNED_TOKEN;
+
         /** @hide */
         @EventFlags
         public int mFlags;
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index 2c021cc..9d43dd3 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -35,6 +35,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 /**
@@ -52,6 +53,11 @@
     /**
      * {@hide}
      */
+    public int mPackageToken = -1;
+
+    /**
+     * {@hide}
+     */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public long mBeginTimeStamp;
 
@@ -143,6 +149,11 @@
     /**
      * {@hide}
      */
+    public SparseArray<SparseIntArray> mChooserCountsObfuscated = new SparseArray<>();
+
+    /**
+     * {@hide}
+     */
     public UsageStats() {
     }
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cb61863..9a9fc89 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -75,6 +75,7 @@
 import android.view.textclassifier.TextClassificationManager;
 
 import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.compat.IPlatformCompatNative;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -3255,6 +3256,7 @@
             //@hide ROLE_CONTROLLER_SERVICE,
             CAMERA_SERVICE,
             //@hide: PLATFORM_COMPAT_SERVICE,
+            //@hide: PLATFORM_COMPAT_NATIVE_SERVICE,
             PRINT_SERVICE,
             CONSUMER_IR_SERVICE,
             //@hide: TRUST_SERVICE,
@@ -4632,6 +4634,14 @@
     public static final String PLATFORM_COMPAT_SERVICE = "platform_compat";
 
     /**
+     * Use with {@link android.os.ServiceManager.getService()} to retrieve a
+     * {@link IPlatformCompatNative} IBinder for native code communicating with the platform compat
+     * service.
+     * @hide
+     */
+    public static final String PLATFORM_COMPAT_NATIVE_SERVICE = "platform_compat_native";
+
+    /**
      * Service to capture a bugreport.
      * @see #getSystemService(String)
      * @see android.os.BugreportManager
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2ecfc8e..4e7e713 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2678,6 +2678,9 @@
      * that application is first launched (that is the first time it is moved
      * out of the stopped state).  The data contains the name of the package.
      *
+     * <p>When the application is first launched, the application itself doesn't receive this
+     * broadcast.</p>
+     *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
      */
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index ccfa184..1e88ce7 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -312,7 +312,7 @@
      */
     public boolean supportsSwitchToByUser() {
         // Hide the system user when it does not represent a human user.
-        boolean hideSystemUser = UserManager.isSplitSystemUser();
+        boolean hideSystemUser = UserManager.isHeadlessSystemUserMode();
         return (!hideSystemUser || id != UserHandle.USER_SYSTEM) && supportsSwitchTo();
     }
 
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index 0ec812f..698876b 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -36,23 +36,30 @@
      * @hide
      */
     int TYPE_NONE = 0;
+
+    /**
+     * Constant representing credential (PIN, pattern, or password).
+     * @hide
+     */
+    int TYPE_CREDENTIAL = 1 << 0;
+
     /**
      * Constant representing fingerprint.
      * @hide
      */
-    int TYPE_FINGERPRINT = 1 << 0;
+    int TYPE_FINGERPRINT = 1 << 1;
 
     /**
      * Constant representing iris.
      * @hide
      */
-    int TYPE_IRIS = 1 << 1;
+    int TYPE_IRIS = 1 << 2;
 
     /**
      * Constant representing face.
      * @hide
      */
-    int TYPE_FACE = 1 << 2;
+    int TYPE_FACE = 1 << 3;
 
     /**
      * Container for biometric data
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 27c04b4..c8bf570 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -134,6 +134,13 @@
     int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
 
     /**
+     * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
+     * because the authentication attempt was unsuccessful.
+     * @hide
+     */
+    int BIOMETRIC_PAUSED_REJECTED = 100;
+
+    /**
      * @hide
      */
     @UnsupportedAppUsage
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index cbe8a05..9d427c8 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -137,7 +137,7 @@
     public boolean hasEnrolledBiometrics(int userId) {
         if (mService != null) {
             try {
-                return mService.hasEnrolledBiometrics(userId);
+                return mService.hasEnrolledBiometrics(userId, mContext.getOpPackageName());
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in hasEnrolledBiometrics(): " + e);
                 return false;
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index cf86e251..9c51b52 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -26,6 +26,8 @@
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -339,9 +341,23 @@
         }
 
         @Override
-        public void onError(int error, String message) throws RemoteException {
+        public void onError(int modality, int error, int vendorCode) throws RemoteException {
             mExecutor.execute(() -> {
-                mAuthenticationCallback.onAuthenticationError(error, message);
+                String errorMessage;
+                switch (modality) {
+                    case TYPE_FACE:
+                        errorMessage = FaceManager.getErrorString(mContext, error, vendorCode);
+                        break;
+
+                    case TYPE_FINGERPRINT:
+                        errorMessage = FingerprintManager.getErrorString(mContext, error,
+                                vendorCode);
+                        break;
+
+                    default:
+                        errorMessage = "";
+                }
+                mAuthenticationCallback.onAuthenticationError(error, errorMessage);
             });
         }
 
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
new file mode 100644
index 0000000..987d197
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.biometrics;
+
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.face.IFaceServiceReceiver;
+import android.hardware.face.Face;
+
+/**
+ * This interface encapsulates fingerprint, face, iris, etc. authenticators.
+ * Implementations of this interface are meant to be registered with BiometricService.
+ * @hide
+ */
+interface IBiometricAuthenticator {
+
+    // This method prepares the service to start authenticating, but doesn't start authentication.
+    // This is protected by the MANAGE_BIOMETRIC signature permission. This method should only be
+    // called from BiometricService. The additional uid, pid, userId arguments should be determined
+    // by BiometricService. To start authentication after the clients are ready, use
+    // startPreparedClient().
+    void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId,
+            int userId, IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
+            int cookie, int callingUid, int callingPid, int callingUserId);
+
+    // Starts authentication with the previously prepared client.
+    void startPreparedClient(int cookie);
+
+    // Same as above, with extra arguments.
+    void cancelAuthenticationFromService(IBinder token, String opPackageName,
+            int callingUid, int callingPid, int callingUserId, boolean fromClient);
+
+    // Determine if HAL is loaded and ready
+    boolean isHardwareDetected(String opPackageName);
+
+    // Determine if a user has at least one enrolled face
+    boolean hasEnrolledTemplates(int userId, String opPackageName);
+
+    // Reset the lockout when user authenticates with strong auth (e.g. PIN, pattern or password)
+    void resetLockout(in byte [] token);
+
+    // Explicitly set the active user (for enrolling work profile)
+    void setActiveUser(int uid);
+}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 6a3bf38..06336a5 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -19,6 +19,7 @@
 import android.os.Bundle;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricServiceReceiver;
+import android.hardware.biometrics.IBiometricAuthenticator;
 
 /**
  * Communication channel from BiometricPrompt and BiometricManager to BiometricService. The
@@ -40,7 +41,12 @@
     int canAuthenticate(String opPackageName, int userId);
 
     // Checks if any biometrics are enrolled.
-    boolean hasEnrolledBiometrics(int userId);
+    boolean hasEnrolledBiometrics(int userId, String opPackageName);
+
+    // 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);
 
     // Register callback for when keyguard biometric eligibility changes.
     void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index 22ef33e..c960049 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -25,7 +25,7 @@
     // Noties that authentication failed.
     void onAuthenticationFailed();
     // Notify BiometricPrompt that an error has occurred.
-    void onError(int error, String message);
+    void onError(int modality, int error, int vendorCode);
     // Notifies that a biometric has been acquired.
     void onAcquired(int acquiredInfo, String message);
     // Notifies that the SystemUI dialog has been dismissed.
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
index 66b6e89..61310f3 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiverInternal.aidl
@@ -31,7 +31,7 @@
     void onAuthenticationFailed();
     // Notify BiometricService than an error has occured. Forward to the correct receiver depending
     // on the cookie.
-    void onError(int cookie, int error, String message);
+    void onError(int cookie, int modality, int error, int vendorCode);
     // Notifies that a biometric has been acquired.
     void onAcquired(int acquiredInfo, String message);
     // Notifies that the SystemUI dialog has been dismissed.
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 12b285a..55ebe28 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -504,8 +504,7 @@
     public boolean isHardwareDetected() {
         if (mService != null) {
             try {
-                long deviceId = 0; /* TODO: plumb hardware id to FPMS */
-                return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
+                return mService.isHardwareDetected(mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index b6a0afb..68a4aef 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -67,7 +67,7 @@
     List<Face> getEnrolledFaces(int userId, String opPackageName);
 
     // Determine if HAL is loaded and ready
-    boolean isHardwareDetected(long deviceId, String opPackageName);
+    boolean isHardwareDetected(String opPackageName);
 
     // Get a pre-enrollment authentication token
     long generateChallenge(IBinder token);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index d0622c8..22f8590 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -691,8 +691,7 @@
     public boolean isHardwareDetected() {
         if (mService != null) {
             try {
-                long deviceId = 0; /* TODO: plumb hardware id to FPMS */
-                return mService.isHardwareDetected(deviceId, mContext.getOpPackageName());
+                return mService.isHardwareDetected(mContext.getOpPackageName());
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index dd6b29d..1a7e128 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -71,7 +71,7 @@
     List<Fingerprint> getEnrolledFingerprints(int groupId, String opPackageName);
 
     // Determine if HAL is loaded and ready
-    boolean isHardwareDetected(long deviceId, String opPackageName);
+    boolean isHardwareDetected(String opPackageName);
 
     // Get a pre-enrollment authentication token
     long preEnroll(IBinder token);
diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java
index 11f4ffb..ee2e262 100644
--- a/core/java/android/hardware/usb/UsbDevice.java
+++ b/core/java/android/hardware/usb/UsbDevice.java
@@ -63,6 +63,8 @@
     private final boolean mHasAudioPlayback;
     private final boolean mHasAudioCapture;
     private final boolean mHasMidi;
+    private final boolean mHasVideoPlayback;
+    private final boolean mHasVideoCapture;
 
     /** All interfaces on the device. Initialized on first call to getInterfaceList */
     @UnsupportedAppUsage
@@ -77,7 +79,8 @@
             int protocol, @Nullable String manufacturerName, @Nullable String productName,
             @NonNull String version, @NonNull UsbConfiguration[] configurations,
             @NonNull IUsbSerialReader serialNumberReader,
-            boolean hasAudioPlayback, boolean hasAudioCapture, boolean hasMidi) {
+            boolean hasAudioPlayback, boolean hasAudioCapture, boolean hasMidi,
+            boolean hasVideoPlayback, boolean hasVideoCapture) {
         mName = Preconditions.checkNotNull(name);
         mVendorId = vendorId;
         mProductId = productId;
@@ -92,6 +95,8 @@
         mHasAudioPlayback = hasAudioPlayback;
         mHasAudioCapture = hasAudioCapture;
         mHasMidi = hasMidi;
+        mHasVideoPlayback = hasVideoPlayback;
+        mHasVideoCapture = hasVideoCapture;
 
         // Make sure the binder belongs to the system
         if (ActivityThread.isSystem()) {
@@ -236,6 +241,16 @@
         return mHasMidi;
     }
 
+    /** @hide */
+    public boolean getHasVideoPlayback() {
+        return mHasVideoPlayback;
+    }
+
+    /** @hide */
+    public boolean getHasVideoCapture() {
+        return mHasVideoCapture;
+    }
+
     /**
      * Returns the {@link UsbConfiguration} at the given index.
      *
@@ -316,6 +331,8 @@
                 + ", mHasAudioPlayback=" + mHasAudioPlayback
                 + ", mHasAudioCapture=" + mHasAudioCapture
                 + ", mHasMidi=" + mHasMidi
+                + ", mHasVideoCapture=" + mHasVideoCapture
+                + ", mHasVideoPlayback=" + mHasVideoPlayback
                 + ", mConfigurations=[");
         for (int i = 0; i < mConfigurations.length; i++) {
             builder.append("\n");
@@ -345,10 +362,12 @@
             boolean hasAudioPlayback = in.readInt() == 1;
             boolean hasAudioCapture = in.readInt() == 1;
             boolean hasMidi = in.readInt() == 1;
-
+            boolean hasVideoPlayback = in.readInt() == 1;
+            boolean hasVideoCapture = in.readInt() == 1;
             UsbDevice device = new UsbDevice(name, vendorId, productId, clasz, subClass, protocol,
                     manufacturerName, productName, version, configurations, serialNumberReader,
-                    hasAudioPlayback, hasAudioCapture, hasMidi);
+                    hasAudioPlayback, hasAudioCapture, hasMidi,
+                    hasVideoPlayback, hasVideoCapture);
 
             return device;
         }
@@ -377,6 +396,8 @@
         parcel.writeInt(mHasAudioPlayback ? 1 : 0);
         parcel.writeInt(mHasAudioCapture ? 1 : 0);
         parcel.writeInt(mHasMidi ? 1 : 0);
+        parcel.writeInt(mHasVideoPlayback ? 1 : 0);
+        parcel.writeInt(mHasVideoCapture ? 1 : 0);
     }
 
     public static int getDeviceId(String name) {
@@ -407,6 +428,8 @@
         private final boolean mHasAudioPlayback;
         private final boolean mHasAudioCapture;
         private final boolean mHasMidi;
+        private final boolean mHasVideoPlayback;
+        private final boolean mHasVideoCapture;
 
         // Temporary storage for serial number. Serial number reader need to be wrapped in a
         // IUsbSerialReader as they might be used as PII.
@@ -416,7 +439,8 @@
                 int protocol, @Nullable String manufacturerName, @Nullable String productName,
                 @NonNull String version, @NonNull UsbConfiguration[] configurations,
                 @Nullable String serialNumber,
-                boolean hasAudioPlayback, boolean hasAudioCapture, boolean hasMidi) {
+                boolean hasAudioPlayback, boolean hasAudioCapture, boolean hasMidi,
+                boolean hasVideoPlayback, boolean hasVideoCapture) {
             mName = Preconditions.checkNotNull(name);
             mVendorId = vendorId;
             mProductId = productId;
@@ -431,6 +455,8 @@
             mHasAudioPlayback = hasAudioPlayback;
             mHasAudioCapture = hasAudioCapture;
             mHasMidi = hasMidi;
+            mHasVideoPlayback = hasVideoPlayback;
+            mHasVideoCapture = hasVideoCapture;
         }
 
         /**
@@ -443,7 +469,8 @@
         public UsbDevice build(@NonNull IUsbSerialReader serialReader) {
             return new UsbDevice(mName, mVendorId, mProductId, mClass, mSubclass, mProtocol,
                     mManufacturerName, mProductName, mVersion, mConfigurations, serialReader,
-                    mHasAudioPlayback, mHasAudioCapture, mHasMidi);
+                    mHasAudioPlayback, mHasAudioCapture, mHasMidi,
+                    mHasVideoPlayback, mHasVideoCapture);
         }
     }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 83391f3..43842c5 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -94,6 +94,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
 
 /**
  * InputMethodService provides a standard implementation of an InputMethod,
@@ -434,6 +435,7 @@
     final int[] mTmpLocation = new int[2];
 
     final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
+        onComputeInsets(mTmpInsets);
         if (isExtractViewShown()) {
             // In true fullscreen mode, we just say the window isn't covering
             // any content so we don't impact whatever is behind.
@@ -442,12 +444,15 @@
             info.touchableRegion.setEmpty();
             info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
         } else {
-            onComputeInsets(mTmpInsets);
             info.contentInsets.top = mTmpInsets.contentTopInsets;
             info.visibleInsets.top = mTmpInsets.visibleTopInsets;
             info.touchableRegion.set(mTmpInsets.touchableRegion);
             info.setTouchableInsets(mTmpInsets.touchableInsets);
         }
+
+        if (mInputFrame != null) {
+            setImeExclusionRect(mTmpInsets.visibleTopInsets);
+        }
     };
 
     final View.OnClickListener mActionClickListener = v -> {
@@ -672,6 +677,14 @@
         mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition);
     }
 
+    /** Set region of the keyboard to be avoided from back gesture */
+    private void setImeExclusionRect(int visibleTopInsets) {
+        View inputFrameRootView = mInputFrame.getRootView();
+        Rect r = new Rect(0, visibleTopInsets, inputFrameRootView.getWidth(),
+                inputFrameRootView.getHeight());
+        inputFrameRootView.setSystemGestureExclusionRects(Collections.singletonList(r));
+    }
+
     /**
      * Concrete implementation of
      * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 3e325b7..e3259ff 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -57,6 +57,9 @@
     private static final String TAG = "NetworkCapabilities";
     private static final int INVALID_UID = -1;
 
+    // Set to true when private DNS is broken.
+    private boolean mPrivateDnsBroken;
+
     /**
      * @hide
      */
@@ -86,6 +89,7 @@
         mUids = null;
         mEstablishingVpnAppUid = INVALID_UID;
         mSSID = null;
+        mPrivateDnsBroken = false;
     }
 
     /**
@@ -104,6 +108,7 @@
         mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
         mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
         mSSID = nc.mSSID;
+        mPrivateDnsBroken = nc.mPrivateDnsBroken;
     }
 
     /**
@@ -557,6 +562,9 @@
         }
         if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth";
         if (hasSignalStrength()) return "signalStrength";
+        if (isPrivateDnsBroken()) {
+            return "privateDnsBroken";
+        }
         return null;
     }
 
@@ -1443,7 +1451,8 @@
                 && equalsSpecifier(that)
                 && equalsTransportInfo(that)
                 && equalsUids(that)
-                && equalsSSID(that));
+                && equalsSSID(that)
+                && equalsPrivateDnsBroken(that));
     }
 
     @Override
@@ -1460,7 +1469,8 @@
                 + (mSignalStrength * 29)
                 + Objects.hashCode(mUids) * 31
                 + Objects.hashCode(mSSID) * 37
-                + Objects.hashCode(mTransportInfo) * 41;
+                + Objects.hashCode(mTransportInfo) * 41
+                + Objects.hashCode(mPrivateDnsBroken) * 43;
     }
 
     @Override
@@ -1479,6 +1489,7 @@
         dest.writeInt(mSignalStrength);
         dest.writeArraySet(mUids);
         dest.writeString(mSSID);
+        dest.writeBoolean(mPrivateDnsBroken);
     }
 
     public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
@@ -1498,6 +1509,7 @@
                 netCap.mUids = (ArraySet<UidRange>) in.readArraySet(
                         null /* ClassLoader, null for default */);
                 netCap.mSSID = in.readString();
+                netCap.mPrivateDnsBroken = in.readBoolean();
                 return netCap;
             }
             @Override
@@ -1555,6 +1567,10 @@
             sb.append(" SSID: ").append(mSSID);
         }
 
+        if (mPrivateDnsBroken) {
+            sb.append(" Private DNS is broken");
+        }
+
         sb.append("]");
         return sb.toString();
     }
@@ -1706,4 +1722,28 @@
     public boolean isMetered() {
         return !hasCapability(NET_CAPABILITY_NOT_METERED);
     }
+
+    /**
+     * Check if private dns is broken.
+     *
+     * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken.
+     * @hide
+     */
+    public boolean isPrivateDnsBroken() {
+        return mPrivateDnsBroken;
+    }
+
+    /**
+     * Set mPrivateDnsBroken to true when private dns is broken.
+     *
+     * @param broken the status of private DNS to be set.
+     * @hide
+     */
+    public void setPrivateDnsBroken(boolean broken) {
+        mPrivateDnsBroken = broken;
+    }
+
+    private boolean equalsPrivateDnsBroken(NetworkCapabilities nc) {
+        return mPrivateDnsBroken == nc.mPrivateDnsBroken;
+    }
 }
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 9ba3bd9..4ad52d5 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -77,6 +77,12 @@
      */
     public boolean skip464xlat;
 
+    /**
+     * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
+     * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
+     */
+    public boolean hasShownBroken;
+
     public NetworkMisc() {
     }
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c6b63ca..71b94ed 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1748,8 +1748,30 @@
         }
     }
 
-    /** {@hide} */
-    public boolean isUserUnlockingOrUnlocked(UserHandle user) {
+    /**
+     * Return whether the provided user is already running in an
+     * "unlocked" state or in the process of unlocking.
+     * <p>
+     * On devices with direct boot, a user is unlocked only after they've
+     * entered their credentials (such as a lock pattern or PIN). On devices
+     * without direct boot, a user is unlocked as soon as it starts.
+     * <p>
+     * When a user is locked, only device-protected data storage is available.
+     * When a user is unlocked, both device-protected and credential-protected
+     * private app data storage is available.
+     *
+     * <p>Requires {@code android.permission.MANAGE_USERS} or
+     * {@code android.permission.INTERACT_ACROSS_USERS}, otherwise specified {@link UserHandle user}
+     * must be the calling user or a profile associated with it.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.INTERACT_ACROSS_USERS
+    })
+    public boolean isUserUnlockingOrUnlocked(@NonNull UserHandle user) {
         return isUserUnlockingOrUnlocked(user.getIdentifier());
     }
 
@@ -2568,6 +2590,22 @@
     }
 
     /**
+     * Checks if the 2 provided user handles belong to the same profile group.
+     *
+     * @param user one of the two user handles to check.
+     * @param otherUser one of the two user handles to check.
+     * @return true if the two users are in the same profile group.
+     *
+     * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isSameProfileGroup(@NonNull UserHandle user, @NonNull UserHandle otherUser) {
+        return isSameProfileGroup(user.getIdentifier(), otherUser.getIdentifier());
+    }
+
+    /**
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      * @param userId one of the two user ids to check.
      * @param otherUserId one of the two user ids to check.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 800c15c..cff99f3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7319,6 +7319,19 @@
         public static final String SILENCE_CALL_TOUCH_COUNT = "silence_call_touch_count";
 
         /**
+         * Number of successful "Motion Sense" tap gestures to pause media.
+         * @hide
+         */
+        public static final String AWARE_TAP_PAUSE_GESTURE_COUNT = "aware_tap_pause_gesture_count";
+
+        /**
+         * Number of touch interactions to pause media when a "Motion Sense" gesture could
+         * have been used.
+         * @hide
+         */
+        public static final String AWARE_TAP_PAUSE_TOUCH_COUNT = "aware_tap_pause_touch_count";
+
+        /**
          * The current night mode that has been selected by the user.  Owned
          * and controlled by UiModeManagerService.  Constants are as per
          * UiModeManager.
@@ -8581,13 +8594,19 @@
         public static final String POWER_SOUNDS_ENABLED = "power_sounds_enabled";
 
         /**
-         * URI for the "wireless charging started" and "wired charging started" sound.
+         * URI for the "wireless charging started" sound.
          * @hide
          */
-        public static final String CHARGING_STARTED_SOUND =
+        public static final String WIRELESS_CHARGING_STARTED_SOUND =
                 "wireless_charging_started_sound";
 
         /**
+         * URI for "wired charging started" sound.
+         * @hide
+         */
+        public static final String CHARGING_STARTED_SOUND = "charging_started_sound";
+
+        /**
          * Whether to play a sound for charging events.
          * @deprecated Use {@link android.provider.Settings.Secure#CHARGING_SOUNDS_ENABLED} instead
          * @hide
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 94b9d05..48ba429 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -181,6 +181,23 @@
     public static final int SAVE_DATA_TYPE_EMAIL_ADDRESS = 0x10;
 
     /**
+     * Type used when the {@link FillResponse} represents a debit card.
+     */
+    public static final int SAVE_DATA_TYPE_DEBIT_CARD = 0x20;
+
+    /**
+     * Type used when the {@link FillResponse} represents a payment card except for credit and
+     * debit cards.
+     */
+    public static final int SAVE_DATA_TYPE_PAYMENT_CARD = 0x40;
+
+    /**
+     * Type used when the {@link FillResponse} represents a card that does not a specified card or
+     * cannot identify what the card is for.
+     */
+    public static final int SAVE_DATA_TYPE_GENERIC_CARD = 0x80;
+
+    /**
      * Style for the negative button of the save UI to cancel the
      * save operation. In this case, the user tapping the negative
      * button signals that they would prefer to not save the filled
@@ -207,6 +224,30 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface NegativeButtonStyle{}
 
+    /**
+     * Style for the positive button of save UI to request the save operation.
+     * In this case, the user tapping the positive button signals that they
+     * agrees to save the filled content.
+     */
+    public static final int POSITIVE_BUTTON_STYLE_SAVE = 0;
+
+    /**
+     * Style for the positive button of save UI to have next action before the save operation.
+     * This could be useful if the filled content contains sensitive personally identifiable
+     * information and then requires user confirmation or verification. In this case, the user
+     * tapping the positive button signals that they would complete the next required action
+     * to save the filled content.
+     */
+    public static final int POSITIVE_BUTTON_STYLE_CONTINUE = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "POSITIVE_BUTTON_STYLE_" }, value = {
+            POSITIVE_BUTTON_STYLE_SAVE,
+            POSITIVE_BUTTON_STYLE_CONTINUE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface PositiveButtonStyle{}
+
     /** @hide */
     @IntDef(flag = true, prefix = { "SAVE_DATA_TYPE_" }, value = {
             SAVE_DATA_TYPE_GENERIC,
@@ -214,7 +255,10 @@
             SAVE_DATA_TYPE_ADDRESS,
             SAVE_DATA_TYPE_CREDIT_CARD,
             SAVE_DATA_TYPE_USERNAME,
-            SAVE_DATA_TYPE_EMAIL_ADDRESS
+            SAVE_DATA_TYPE_EMAIL_ADDRESS,
+            SAVE_DATA_TYPE_DEBIT_CARD,
+            SAVE_DATA_TYPE_PAYMENT_CARD,
+            SAVE_DATA_TYPE_GENERIC_CARD
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface SaveDataType{}
@@ -266,6 +310,7 @@
 
     private final @SaveDataType int mType;
     private final @NegativeButtonStyle int mNegativeButtonStyle;
+    private final @PositiveButtonStyle int mPositiveButtonStyle;
     private final IntentSender mNegativeActionListener;
     private final AutofillId[] mRequiredIds;
     private final AutofillId[] mOptionalIds;
@@ -281,6 +326,7 @@
         mType = builder.mType;
         mNegativeButtonStyle = builder.mNegativeButtonStyle;
         mNegativeActionListener = builder.mNegativeActionListener;
+        mPositiveButtonStyle = builder.mPositiveButtonStyle;
         mRequiredIds = builder.mRequiredIds;
         mOptionalIds = builder.mOptionalIds;
         mDescription = builder.mDescription;
@@ -313,6 +359,11 @@
     }
 
     /** @hide */
+    public @PositiveButtonStyle int getPositiveActionStyle() {
+        return mPositiveButtonStyle;
+    }
+
+    /** @hide */
     public @Nullable AutofillId[] getRequiredIds() {
         return mRequiredIds;
     }
@@ -374,6 +425,7 @@
 
         private final @SaveDataType int mType;
         private @NegativeButtonStyle int mNegativeButtonStyle = NEGATIVE_BUTTON_STYLE_CANCEL;
+        private @PositiveButtonStyle int mPositiveButtonStyle = POSITIVE_BUTTON_STYLE_SAVE;
         private IntentSender mNegativeActionListener;
         private final AutofillId[] mRequiredIds;
         private AutofillId[] mOptionalIds;
@@ -394,8 +446,9 @@
          * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
          * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
          * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
-         * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
-         * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
+         * {@link SaveInfo#SAVE_DATA_TYPE_DEBIT_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_PAYMENT_CARD},
+         * {@link SaveInfo#SAVE_DATA_TYPE_GENERIC_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_USERNAME},
+         * or {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
          * @param requiredIds ids of all required views that will trigger a save request.
          *
          * <p>See {@link SaveInfo} for more info.
@@ -418,8 +471,9 @@
          * can be any combination of {@link SaveInfo#SAVE_DATA_TYPE_GENERIC},
          * {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD},
          * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD},
-         * {@link SaveInfo#SAVE_DATA_TYPE_USERNAME}, or
-         * {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
+         * {@link SaveInfo#SAVE_DATA_TYPE_DEBIT_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_PAYMENT_CARD},
+         * {@link SaveInfo#SAVE_DATA_TYPE_GENERIC_CARD}, {@link SaveInfo#SAVE_DATA_TYPE_USERNAME},
+         * or {@link SaveInfo#SAVE_DATA_TYPE_EMAIL_ADDRESS}.
          *
          * <p>See {@link SaveInfo} for more info.
          */
@@ -523,6 +577,8 @@
         public @NonNull Builder setNegativeAction(@NegativeButtonStyle int style,
                 @Nullable IntentSender listener) {
             throwIfDestroyed();
+            Preconditions.checkArgumentInRange(style, NEGATIVE_BUTTON_STYLE_CANCEL,
+                    NEGATIVE_BUTTON_STYLE_REJECT, "style");
             if (style != NEGATIVE_BUTTON_STYLE_CANCEL
                     && style != NEGATIVE_BUTTON_STYLE_REJECT) {
                 throw new IllegalArgumentException("Invalid style: " + style);
@@ -533,6 +589,33 @@
         }
 
         /**
+         * Sets the style for the positive save action.
+         *
+         * <p>This allows an autofill service to customize the style of the
+         * positive action in the save UI. Note that selecting the positive
+         * action regardless of its style would dismiss the save UI and calling
+         * into the {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback) save request}.
+         * The service should take the next action if selecting style
+         * {@link #POSITIVE_BUTTON_STYLE_CONTINUE}. The default style is
+         * {@link #POSITIVE_BUTTON_STYLE_SAVE}
+         *
+         * @param style The action style.
+         * @return This builder.
+         *
+         * @see #POSITIVE_BUTTON_STYLE_SAVE
+         * @see #POSITIVE_BUTTON_STYLE_CONTINUE
+         *
+         * @throws IllegalArgumentException If the style is invalid
+         */
+        public @NonNull Builder setPositiveAction(@PositiveButtonStyle int style) {
+            throwIfDestroyed();
+            Preconditions.checkArgumentInRange(style, POSITIVE_BUTTON_STYLE_SAVE,
+                    POSITIVE_BUTTON_STYLE_CONTINUE, "style");
+            mPositiveButtonStyle = style;
+            return this;
+        }
+
+        /**
          * Sets an object used to validate the user input - if the input is not valid, the
          * autofill save UI is not shown.
          *
@@ -717,8 +800,10 @@
         final StringBuilder builder = new StringBuilder("SaveInfo: [type=")
                 .append(DebugUtils.flagsToString(SaveInfo.class, "SAVE_DATA_TYPE_", mType))
                 .append(", requiredIds=").append(Arrays.toString(mRequiredIds))
-                .append(", style=").append(DebugUtils.flagsToString(SaveInfo.class,
-                        "NEGATIVE_BUTTON_STYLE_", mNegativeButtonStyle));
+                .append(", negative style=").append(DebugUtils.flagsToString(SaveInfo.class,
+                        "NEGATIVE_BUTTON_STYLE_", mNegativeButtonStyle))
+                .append(", positive style=").append(DebugUtils.flagsToString(SaveInfo.class,
+                        "POSITIVE_BUTTON_STYLE_", mPositiveButtonStyle));
         if (mOptionalIds != null) {
             builder.append(", optionalIds=").append(Arrays.toString(mOptionalIds));
         }
@@ -763,6 +848,7 @@
         parcel.writeParcelableArray(mOptionalIds, flags);
         parcel.writeInt(mNegativeButtonStyle);
         parcel.writeParcelable(mNegativeActionListener, flags);
+        parcel.writeInt(mPositiveButtonStyle);
         parcel.writeCharSequence(mDescription);
         parcel.writeParcelable(mCustomDescription, flags);
         parcel.writeParcelable(mValidator, flags);
@@ -794,6 +880,7 @@
             }
 
             builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
+            builder.setPositiveAction(parcel.readInt());
             builder.setDescription(parcel.readCharSequence());
             final CustomDescription customDescripton = parcel.readParcelable(null);
             if (customDescripton != null) {
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 8a9f689..12c2580 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -516,7 +516,7 @@
      * @see android.telephony.euicc.EuiccManager#eraseSubscriptions
      *
      * @deprecated From R, callers should specify a flag for specific set of subscriptions to erase
-     * and use @link{onEraseSubscriptionsWithOptions} instead
+     * and use {@link #onEraseSubscriptionsWithOptions(int, int)} instead
      */
     @Deprecated
     public abstract int onEraseSubscriptions(int slotId);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 1be57dd..b1fd4e5 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -41,14 +41,12 @@
     public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
     public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
     public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2";
-    public static final String USE_BUGREPORT_API = "settings_use_bugreport_api";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
         DEFAULT_FLAGS = new HashMap<>();
         DEFAULT_FLAGS.put("settings_audio_switcher", "true");
-        DEFAULT_FLAGS.put("settings_use_bugreport_api", "true");
         DEFAULT_FLAGS.put("settings_mobile_network_v2", "true");
         DEFAULT_FLAGS.put("settings_network_and_internet_v2", "true");
         DEFAULT_FLAGS.put("settings_systemui_theme", "true");
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index b9a92b5..28a8a86 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -274,6 +274,7 @@
             // Fall into normal sort when number of ranked elements
             // needed is not smaller than size of input list.
             sort(inputList);
+            return;
         }
         try {
             long beforeRank = System.currentTimeMillis();
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index 72b0ad7..e0eb9af 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -21,6 +21,7 @@
 import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -37,7 +38,7 @@
     private static final String TAG = "CompatibilityChangeReporter";
     private int mSource;
 
-    private final class ChangeReport {
+    private static final class ChangeReport {
         long mChangeId;
         int mState;
 
@@ -65,9 +66,13 @@
     @GuardedBy("mReportedChanges")
     private final Map<Integer, Set<ChangeReport>> mReportedChanges;
 
+    // When true will of every time to debug (logcat).
+    private boolean mDebugLogAll;
+
     public ChangeReporter(int source) {
         mSource = source;
         mReportedChanges =  new HashMap<>();
+        mDebugLogAll = false;
     }
 
     /**
@@ -79,20 +84,76 @@
      * @param state    of the reported change - enabled/disabled/only logged
      */
     public void reportChange(int uid, long changeId, int state) {
-        ChangeReport report = new ChangeReport(changeId, state);
+        if (shouldWriteToStatsLog(uid, changeId, state)) {
+            StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
+                    state, mSource);
+        }
+        if (shouldWriteToDebug(uid, changeId, state)) {
+            debugLog(uid, changeId, state);
+        }
+        markAsReported(uid, new ChangeReport(changeId, state));
+    }
+
+    /**
+     * Start logging all the time to logcat.
+     */
+    public void startDebugLogAll() {
+        mDebugLogAll = true;
+    }
+
+    /**
+     * Stop logging all the time to logcat.
+     */
+    public void stopDebugLogAll() {
+        mDebugLogAll = false;
+    }
+
+
+    /**
+     * Returns whether the next report should be logged to statsLog.
+     *
+     * @param uid      affected by the change
+     * @param changeId the reported change id
+     * @param state    of the reported change - enabled/disabled/only logged
+     * @return true if the report should be logged
+     */
+    @VisibleForTesting
+    public boolean shouldWriteToStatsLog(int uid, long changeId, int state) {
+        return !isAlreadyReported(uid, new ChangeReport(changeId, state));
+    }
+
+    /**
+     * Returns whether the next report should be logged to logcat.
+     *
+     * @param uid      affected by the change
+     * @param changeId the reported change id
+     * @param state    of the reported change - enabled/disabled/only logged
+     * @return true if the report should be logged
+     */
+    @VisibleForTesting
+    public boolean shouldWriteToDebug(int uid, long changeId, int state) {
+        return mDebugLogAll || !isAlreadyReported(uid, new ChangeReport(changeId, state));
+    }
+
+    private boolean isAlreadyReported(int uid, ChangeReport report) {
+        synchronized (mReportedChanges) {
+            Set<ChangeReport> reportedChangesForUid = mReportedChanges.get(uid);
+            if (reportedChangesForUid == null) {
+                return false;
+            } else {
+                return reportedChangesForUid.contains(report);
+            }
+        }
+    }
+
+    private void markAsReported(int uid, ChangeReport report) {
         synchronized (mReportedChanges) {
             Set<ChangeReport> reportedChangesForUid = mReportedChanges.get(uid);
             if (reportedChangesForUid == null) {
                 mReportedChanges.put(uid, new HashSet<ChangeReport>());
                 reportedChangesForUid = mReportedChanges.get(uid);
             }
-            if (!reportedChangesForUid.contains(report)) {
-                debugLog(uid, changeId, state);
-                StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
-                        state, mSource);
-                reportedChangesForUid.add(report);
-            }
-
+            reportedChangesForUid.add(report);
         }
     }
 
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 4099cfa..8391ad2 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -49,9 +49,10 @@
      * you do not need to call this API directly. The change will be reported for you.
      *
      * @param changeId    The ID of the compatibility change taking effect.
+     * @param userId      The ID of the user that the operation is done for.
      * @param packageName The package name of the app in question.
      */
-     void reportChangeByPackageName(long changeId, in String packageName);
+     void reportChangeByPackageName(long changeId, in String packageName, int userId);
 
     /**
      * Reports that a compatibility change is affecting an app process now.
@@ -86,7 +87,7 @@
      * be called when implementing functionality on behalf of the affected app.
      *
      * <p>Same as {@link #isChangeEnabled(long, ApplicationInfo)}, except it receives a package name
-     * instead of an {@link ApplicationInfo}
+     * and userId instead of an {@link ApplicationInfo}
      * object, and finds an app info object based on the package name. Returns {@code true} if
      * there is no installed package by that name.
      *
@@ -100,9 +101,10 @@
      *
      * @param changeId    The ID of the compatibility change in question.
      * @param packageName The package name of the app in question.
+     * @param userId      The ID of the user that the operation is done for.
      * @return {@code true} if the change is enabled for the current app.
      */
-    boolean isChangeEnabledByPackageName(long changeId, in String packageName);
+    boolean isChangeEnabledByPackageName(long changeId, in String packageName, int userId);
 
     /**
      * Query if a given compatibility change is enabled for an app process. This method should
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index d9e2ba3..317469e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -155,12 +155,12 @@
     // Used to show the authentication dialog (Biometrics, Device Credential)
     void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
             int biometricModality, boolean requireConfirmation, int userId, String opPackageName);
-    // Used to notify the authentication dialog that a biometric has been authenticated or rejected
-    void onBiometricAuthenticated(boolean authenticated, String failureReason);
+    // Used to notify the authentication dialog that a biometric has been authenticated
+    void onBiometricAuthenticated();
     // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
     void onBiometricHelp(String message);
-    // Used to set a message - the dialog will dismiss after a certain amount of time
-    void onBiometricError(int errorCode, String error);
+    // Used to show an error - the dialog will dismiss after a certain amount of time
+    void onBiometricError(int modality, int error, int vendorCode);
     // Used to hide the authentication dialog, e.g. when the application cancels authentication
     void hideAuthenticationDialog();
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 659134a..499a4d2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -104,12 +104,12 @@
     // Used to show the authentication dialog (Biometrics, Device Credential)
     void showAuthenticationDialog(in Bundle bundle, IBiometricServiceReceiverInternal receiver,
             int biometricModality, boolean requireConfirmation, int userId, String opPackageName);
-    // Used to notify the authentication dialog that a biometric has been authenticated or rejected
-    void onBiometricAuthenticated(boolean authenticated, String failureReason);
+    // Used to notify the authentication dialog that a biometric has been authenticated
+    void onBiometricAuthenticated();
     // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc
     void onBiometricHelp(String message);
-    // Used to set a message - the dialog will dismiss after a certain amount of time
-    void onBiometricError(int errorCode, String error);
+    // Used to show an error - the dialog will dismiss after a certain amount of time
+    void onBiometricError(int modality, int error, int vendorCode);
     // Used to hide the authentication dialog, e.g. when the application cancels authentication
     void hideAuthenticationDialog();
 }
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index b626fc6..897b982 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -24,6 +24,7 @@
 import android.security.keystore.recovery.KeyChainProtectionParams;
 import android.security.keystore.recovery.RecoveryCertPath;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 
 import java.util.Map;
@@ -42,19 +43,16 @@
     long getLong(in String key, in long defaultValue, in int userId);
     @UnsupportedAppUsage
     String getString(in String key, in String defaultValue, in int userId);
-    boolean setLockCredential(in byte[] credential, int type, in byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange);
+    boolean setLockCredential(in LockscreenCredential credential, in LockscreenCredential savedCredential, int userId, boolean allowUntrustedChange);
     void resetKeyStore(int userId);
-    VerifyCredentialResponse checkCredential(in byte[] credential, int type, int userId,
+    VerifyCredentialResponse checkCredential(in LockscreenCredential credential, int userId,
             in ICheckCredentialProgressCallback progressCallback);
-    VerifyCredentialResponse verifyCredential(in byte[] credential, int type, long challenge, int userId);
-    VerifyCredentialResponse verifyTiedProfileChallenge(in byte[] credential, int type, long challenge, int userId);
+    VerifyCredentialResponse verifyCredential(in LockscreenCredential credential, long challenge, int userId);
+    VerifyCredentialResponse verifyTiedProfileChallenge(in LockscreenCredential credential, long challenge, int userId);
     boolean checkVoldPassword(int userId);
-    @UnsupportedAppUsage
-    boolean havePattern(int userId);
-    @UnsupportedAppUsage
-    boolean havePassword(int userId);
-    byte[] getHashFactor(in byte[] currentCredential, int userId);
-    void setSeparateProfileChallengeEnabled(int userId, boolean enabled, in byte[] managedUserPassword);
+    int getCredentialType(int userId);
+    byte[] getHashFactor(in LockscreenCredential currentCredential, int userId);
+    void setSeparateProfileChallengeEnabled(int userId, boolean enabled, in LockscreenCredential managedUserPassword);
     boolean getSeparateProfileChallengeEnabled(int userId);
     void registerStrongAuthTracker(in IStrongAuthTracker tracker);
     void unregisterStrongAuthTracker(in IStrongAuthTracker tracker);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 7a42985..b534213 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -17,9 +17,6 @@
 package com.android.internal.widget;
 
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
@@ -59,10 +56,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 
-import libcore.util.HexEncoding;
-
 import com.google.android.collect.Lists;
 
+import libcore.util.HexEncoding;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.MessageDigest;
@@ -117,13 +114,19 @@
     // NOTE: When modifying this, make sure credential sufficiency validation logic is intact.
     public static final int CREDENTIAL_TYPE_NONE = -1;
     public static final int CREDENTIAL_TYPE_PATTERN = 1;
-    public static final int CREDENTIAL_TYPE_PASSWORD = 2;
+    // This is the legacy value persisted on disk. Never return it to clients, but internally
+    // we still need it to handle upgrade cases.
+    public static final int CREDENTIAL_TYPE_PASSWORD_OR_PIN = 2;
+    public static final int CREDENTIAL_TYPE_PIN = 3;
+    public static final int CREDENTIAL_TYPE_PASSWORD = 4;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"CREDENTIAL_TYPE_"}, value = {
             CREDENTIAL_TYPE_NONE,
             CREDENTIAL_TYPE_PATTERN,
-            CREDENTIAL_TYPE_PASSWORD, // Either pin or password.
+            CREDENTIAL_TYPE_PASSWORD,
+            CREDENTIAL_TYPE_PIN,
+            // CREDENTIAL_TYPE_PASSWORD_OR_PIN is missing on purpose.
     })
     public @interface CredentialType {}
 
@@ -169,6 +172,7 @@
 
     public static final String SYNTHETIC_PASSWORD_HANDLE_KEY = "sp-handle";
     public static final String SYNTHETIC_PASSWORD_ENABLED_KEY = "enable-sp";
+    public static final int SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT = 1;
     private static final String HISTORY_DELIMITER = ",";
 
     @UnsupportedAppUsage
@@ -372,8 +376,8 @@
      *
      * @param credential The credential to check.
      * @param challenge The challenge to verify against the credential
-     * @return the attestation that the challenge was verified, or null
      * @param userId The user whose credential is being verified
+     * @return the attestation that the challenge was verified, or null
      * @throws RequestThrottledException if credential verification is being throttled due to
      *         to many incorrect attempts.
      * @throws IllegalStateException if called on the main thread.
@@ -383,7 +387,7 @@
         throwIfCalledOnMainThread();
         try {
             VerifyCredentialResponse response = getLockSettings().verifyCredential(
-                    credential.getCredential(), credential.getType(), challenge, userId);
+                    credential, challenge, userId);
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                 return response.getPayload();
             } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
@@ -392,6 +396,7 @@
                 return null;
             }
         } catch (RemoteException re) {
+            Log.e(TAG, "failed to verify credential", re);
             return null;
         }
     }
@@ -413,8 +418,7 @@
         throwIfCalledOnMainThread();
         try {
             VerifyCredentialResponse response = getLockSettings().checkCredential(
-                    credential.getCredential(), credential.getType(),
-                    userId, wrapCallback(progressCallback));
+                    credential, userId, wrapCallback(progressCallback));
 
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                 return true;
@@ -424,6 +428,7 @@
                 return false;
             }
         } catch (RemoteException re) {
+            Log.e(TAG, "failed to check credential", re);
             return false;
         }
     }
@@ -447,8 +452,7 @@
         throwIfCalledOnMainThread();
         try {
             VerifyCredentialResponse response =
-                    getLockSettings().verifyTiedProfileChallenge(
-                            credential.getCredential(), credential.getType(), challenge, userId);
+                    getLockSettings().verifyTiedProfileChallenge(credential, challenge, userId);
 
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
                 return response.getPayload();
@@ -458,6 +462,7 @@
                 return null;
             }
         } catch (RemoteException re) {
+            Log.e(TAG, "failed to verify tied profile credential", re);
             return null;
         }
     }
@@ -471,6 +476,7 @@
         try {
             return getLockSettings().checkVoldPassword(userId);
         } catch (RemoteException re) {
+            Log.e(TAG, "failed to check vold password", re);
             return false;
         }
     }
@@ -482,7 +488,7 @@
     public byte[] getPasswordHistoryHashFactor(@NonNull LockscreenCredential currentPassword,
             int userId) {
         try {
-            return getLockSettings().getHashFactor(currentPassword.getCredential(), userId);
+            return getLockSettings().getHashFactor(currentPassword, userId);
         } catch (RemoteException e) {
             Log.e(TAG, "failed to get hash factor", e);
             return null;
@@ -524,30 +530,6 @@
     }
 
     /**
-     * Check to see if the user has stored a lock pattern.
-     * @return Whether a saved pattern exists.
-     */
-    private boolean savedPatternExists(int userId) {
-        try {
-            return getLockSettings().havePattern(userId);
-        } catch (RemoteException re) {
-            return false;
-        }
-    }
-
-    /**
-     * Check to see if the user has stored a lock pattern.
-     * @return Whether a saved pattern exists.
-     */
-    private boolean savedPasswordExists(int userId) {
-        try {
-            return getLockSettings().havePassword(userId);
-        } catch (RemoteException re) {
-            return false;
-        }
-    }
-
-    /**
      * Return true if the user has ever chosen a pattern.  This is true even if the pattern is
      * currently cleared.
      *
@@ -568,22 +550,11 @@
     /**
      * Used by device policy manager to validate the current password
      * information it has.
+     * @Deprecated use {@link #getKeyguardStoredPasswordQuality}
      */
     @UnsupportedAppUsage
     public int getActivePasswordQuality(int userId) {
-        int quality = getKeyguardStoredPasswordQuality(userId);
-
-        if (isLockPasswordEnabled(quality, userId)) {
-            // Quality is a password and a password exists. Return the quality.
-            return quality;
-        }
-
-        if (isLockPatternEnabled(quality, userId)) {
-            // Quality is a pattern and a pattern exists. Return the quality.
-            return quality;
-        }
-
-        return PASSWORD_QUALITY_UNSPECIFIED;
+        return getKeyguardStoredPasswordQuality(userId);
     }
 
     /**
@@ -641,6 +612,22 @@
         return quality == PASSWORD_QUALITY_NUMERIC || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX;
     }
 
+    /** Returns the canonical password quality corresponding to the given credential type. */
+    public static int credentialTypeToPasswordQuality(int credentialType) {
+        switch (credentialType) {
+            case CREDENTIAL_TYPE_NONE:
+                return PASSWORD_QUALITY_UNSPECIFIED;
+            case CREDENTIAL_TYPE_PATTERN:
+                return PASSWORD_QUALITY_SOMETHING;
+            case CREDENTIAL_TYPE_PIN:
+                return PASSWORD_QUALITY_NUMERIC;
+            case CREDENTIAL_TYPE_PASSWORD:
+                return PASSWORD_QUALITY_ALPHABETIC;
+            default:
+                throw new IllegalStateException("Unknown type: " + credentialType);
+        }
+    }
+
     /**
      * Save a new lockscreen credential.
      *
@@ -684,19 +671,12 @@
         }
         newCredential.checkLength();
 
-        final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
-        setKeyguardStoredPasswordQuality(newCredential.getQuality(), userHandle);
-
         try {
             if (!getLockSettings().setLockCredential(
-                    newCredential.getCredential(), newCredential.getType(),
-                    savedCredential.getCredential(),
-                    newCredential.getQuality(), userHandle, allowUntrustedChange)) {
-                setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+                    newCredential, savedCredential, userHandle, allowUntrustedChange)) {
                 return false;
             }
-        } catch (RemoteException | RuntimeException e) {
-            setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+        } catch (RemoteException e) {
             throw new RuntimeException("Unable to save lock password", e);
         }
 
@@ -904,14 +884,12 @@
      * @see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)
      *
      * @return stored password quality
+     * @deprecated use {@link #getCredentialTypeForUser(int)} instead
      */
     @UnsupportedAppUsage
+    @Deprecated
     public int getKeyguardStoredPasswordQuality(int userHandle) {
-        return (int) getLong(PASSWORD_TYPE_KEY, PASSWORD_QUALITY_UNSPECIFIED, userHandle);
-    }
-
-    private void setKeyguardStoredPasswordQuality(int quality, int userHandle) {
-        setLong(PASSWORD_TYPE_KEY, quality, userHandle);
+        return credentialTypeToPasswordQuality(getCredentialTypeForUser(userHandle));
     }
 
     /**
@@ -920,17 +898,17 @@
      *
      * @param userHandle Managed profile user id
      * @param enabled True if separate challenge is enabled
-     * @param managedUserPassword Managed profile previous password. Null when {@code enabled} is
+     * @param profilePassword Managed profile previous password. Null when {@code enabled} is
      *            true
      */
     public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled,
-            LockscreenCredential managedUserPassword) {
+            LockscreenCredential profilePassword) {
         if (!isManagedProfile(userHandle)) {
             return;
         }
         try {
             getLockSettings().setSeparateProfileChallengeEnabled(userHandle, enabled,
-                    managedUserPassword.getCredential());
+                    profilePassword);
             reportEnabledTrustAgentsChanged(userHandle);
         } catch (RemoteException e) {
             Log.e(TAG, "Couldn't update work profile challenge enabled");
@@ -1098,28 +1076,33 @@
     }
 
     /**
+     * Returns the credential type of the user, can be one of {@link #CREDENTIAL_TYPE_NONE},
+     * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and
+     * {@link #CREDENTIAL_TYPE_PASSWORD}
+     */
+    public @CredentialType int getCredentialTypeForUser(int userHandle) {
+        try {
+            return getLockSettings().getCredentialType(userHandle);
+        } catch (RemoteException re) {
+            Log.e(TAG, "failed to get credential type", re);
+            return CREDENTIAL_TYPE_NONE;
+        }
+    }
+
+    /**
      * @param userId the user for which to report the value
      * @return Whether the lock screen is secured.
      */
     @UnsupportedAppUsage
     public boolean isSecure(int userId) {
-        int mode = getKeyguardStoredPasswordQuality(userId);
-        return isLockPatternEnabled(mode, userId) || isLockPasswordEnabled(mode, userId);
+        int type = getCredentialTypeForUser(userId);
+        return type != CREDENTIAL_TYPE_NONE;
     }
 
     @UnsupportedAppUsage
     public boolean isLockPasswordEnabled(int userId) {
-        return isLockPasswordEnabled(getKeyguardStoredPasswordQuality(userId), userId);
-    }
-
-    private boolean isLockPasswordEnabled(int mode, int userId) {
-        final boolean passwordEnabled = mode == PASSWORD_QUALITY_ALPHABETIC
-                || mode == PASSWORD_QUALITY_NUMERIC
-                || mode == PASSWORD_QUALITY_NUMERIC_COMPLEX
-                || mode == PASSWORD_QUALITY_ALPHANUMERIC
-                || mode == PASSWORD_QUALITY_COMPLEX
-                || mode == PASSWORD_QUALITY_MANAGED;
-        return passwordEnabled && savedPasswordExists(userId);
+        int type = getCredentialTypeForUser(userId);
+        return type == CREDENTIAL_TYPE_PASSWORD || type == CREDENTIAL_TYPE_PIN;
     }
 
     /**
@@ -1127,7 +1110,8 @@
      */
     @UnsupportedAppUsage
     public boolean isLockPatternEnabled(int userId) {
-        return isLockPatternEnabled(getKeyguardStoredPasswordQuality(userId), userId);
+        int type = getCredentialTypeForUser(userId);
+        return type == CREDENTIAL_TYPE_PATTERN;
     }
 
     @Deprecated
@@ -1143,10 +1127,6 @@
         setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, true, userId);
     }
 
-    private boolean isLockPatternEnabled(int mode, int userId) {
-        return mode == PASSWORD_QUALITY_SOMETHING && savedPatternExists(userId);
-    }
-
     /**
      * @return Whether the visible pattern is enabled.
      */
@@ -1543,8 +1523,8 @@
      * @param userHandle The user who's lock credential to be changed
      * @return {@code true} if the operation is successful.
      */
-    public boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
-            byte[] token, int userHandle) {
+    public boolean setLockCredentialWithToken(@NonNull LockscreenCredential credential,
+            long tokenHandle, byte[] token, int userHandle) {
         if (!hasSecureLockScreen()) {
             throw new UnsupportedOperationException(
                     "This operation requires the lock screen feature.");
@@ -1552,19 +1532,8 @@
         credential.checkLength();
         LockSettingsInternal localService = getLockSettingsInternal();
 
-        final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
-        setKeyguardStoredPasswordQuality(credential.getQuality(), userHandle);
-
-        try {
-            if (!localService.setLockCredentialWithToken(credential.getCredential(),
-                    credential.getType(),
-                    tokenHandle, token, credential.getQuality(), userHandle)) {
-                setKeyguardStoredPasswordQuality(currentQuality, userHandle);
-                return false;
-            }
-        } catch (RuntimeException e) {
-            setKeyguardStoredPasswordQuality(currentQuality, userHandle);
-            throw new RuntimeException("Unable to save lock credential", e);
+        if (!localService.setLockCredentialWithToken(credential, tokenHandle, token, userHandle)) {
+            return false;
         }
 
         onPostPasswordChanged(credential, userHandle);
@@ -1765,7 +1734,8 @@
     }
 
     public boolean isSyntheticPasswordEnabled() {
-        return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
+        return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT,
+                UserHandle.USER_SYSTEM) != 0;
     }
 
     /**
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index aab2f4b..dd05576 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -59,8 +59,8 @@
      *
      * @return true if password is set.
      */
-    public abstract boolean setLockCredentialWithToken(byte[] credential, int type,
-            long tokenHandle, byte[] token, int requestedQuality, int userId);
+    public abstract boolean setLockCredentialWithToken(LockscreenCredential credential,
+            long tokenHandle, byte[] token, int userId);
 
     public abstract boolean unlockUserWithToken(long tokenHandle, byte[] token, int userId);
 
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/core/java/com/android/internal/widget/LockscreenCredential.aidl
similarity index 75%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to core/java/com/android/internal/widget/LockscreenCredential.aidl
index d2ec357..22501ff 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/core/java/com/android/internal/widget/LockscreenCredential.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.internal.widget;
 
-parcelable TimeSignal;
\ No newline at end of file
+/**
+ * A class representing a lockscreen credential.
+ */
+parcelable LockscreenCredential;
+
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index 19e6d97..f456349 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -16,14 +16,11 @@
 
 package com.android.internal.widget;
 
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -61,9 +58,6 @@
     // Stores raw credential bytes, or null if credential has been zeroized. An empty password
     // is represented as a byte array of length 0.
     private byte[] mCredential;
-    // Store the quality of the password, this is used to distinguish between pin
-    // (PASSWORD_QUALITY_NUMERIC) and password (PASSWORD_QUALITY_ALPHABETIC).
-    private final int mQuality;
 
     /**
      * Private constructor, use static builder methods instead.
@@ -72,15 +66,18 @@
      * LockscreenCredential will only store the reference internally without copying. This is to
      * minimize the number of extra copies introduced.
      */
-    private LockscreenCredential(int type, int quality, byte[] credential) {
+    private LockscreenCredential(int type, byte[] credential) {
         Preconditions.checkNotNull(credential);
         if (type == CREDENTIAL_TYPE_NONE) {
             Preconditions.checkArgument(credential.length == 0);
         } else {
+            // Do not allow constructing a CREDENTIAL_TYPE_PASSWORD_OR_PIN object.
+            Preconditions.checkArgument(type == CREDENTIAL_TYPE_PIN
+                    || type == CREDENTIAL_TYPE_PASSWORD
+                    || type == CREDENTIAL_TYPE_PATTERN);
             Preconditions.checkArgument(credential.length > 0);
         }
         mType = type;
-        mQuality = quality;
         mCredential = credential;
     }
 
@@ -88,8 +85,7 @@
      * Creates a LockscreenCredential object representing empty password.
      */
     public static LockscreenCredential createNone() {
-        return new LockscreenCredential(CREDENTIAL_TYPE_NONE, PASSWORD_QUALITY_UNSPECIFIED,
-                new byte[0]);
+        return new LockscreenCredential(CREDENTIAL_TYPE_NONE, new byte[0]);
     }
 
     /**
@@ -97,7 +93,6 @@
      */
     public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
         return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
-                PASSWORD_QUALITY_SOMETHING,
                 LockPatternUtils.patternToByteArray(pattern));
     }
 
@@ -106,16 +101,25 @@
      */
     public static LockscreenCredential createPassword(@NonNull CharSequence password) {
         return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
-                PASSWORD_QUALITY_ALPHABETIC,
                 charSequenceToByteArray(password));
     }
 
     /**
+     * Creates a LockscreenCredential object representing a managed password for profile with
+     * unified challenge. This credentiall will have type {@code CREDENTIAL_TYPE_PASSWORD} for now.
+     * TODO: consider add a new credential type for this. This can then supersede the
+     * isLockTiedToParent argument in various places in LSS.
+     */
+    public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
+        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
+                Arrays.copyOf(password, password.length));
+    }
+
+    /**
      * Creates a LockscreenCredential object representing the given numeric PIN.
      */
     public static LockscreenCredential createPin(@NonNull CharSequence pin) {
-        return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
-                PASSWORD_QUALITY_NUMERIC,
+        return new LockscreenCredential(CREDENTIAL_TYPE_PIN,
                 charSequenceToByteArray(pin));
     }
 
@@ -143,27 +147,13 @@
         }
     }
 
-    /**
-     * Create a LockscreenCredential object based on raw credential and type
-     * TODO: Remove once LSS.setUserPasswordMetrics accepts a LockscreenCredential
-     */
-    public static LockscreenCredential createRaw(int type, byte[] credential) {
-        if (type == CREDENTIAL_TYPE_NONE) {
-            return createNone();
-        } else {
-            return new LockscreenCredential(type, PASSWORD_QUALITY_UNSPECIFIED, credential);
-        }
-    }
-
     private void ensureNotZeroized() {
         Preconditions.checkState(mCredential != null, "Credential is already zeroized");
     }
     /**
      * Returns the type of this credential. Can be one of {@link #CREDENTIAL_TYPE_NONE},
-     * {@link #CREDENTIAL_TYPE_PATTERN} or {@link #CREDENTIAL_TYPE_PASSWORD}.
-     *
-     * TODO: Remove once credential type is internal. Callers should use {@link #isNone},
-     * {@link #isPattern} and {@link #isPassword} instead.
+     * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} or
+     * {@link #CREDENTIAL_TYPE_PASSWORD}.
      */
     public int getType() {
         ensureNotZeroized();
@@ -171,14 +161,6 @@
     }
 
     /**
-     * Returns the quality type of the credential
-     */
-    public int getQuality() {
-        ensureNotZeroized();
-        return mQuality;
-    }
-
-    /**
      * Returns the credential bytes. This is a direct reference of the internal field so
      * callers should not modify it.
      *
@@ -200,9 +182,11 @@
         if (isPattern()) {
             return StorageManager.CRYPT_TYPE_PATTERN;
         }
+        if (isPin()) {
+            return StorageManager.CRYPT_TYPE_PIN;
+        }
         if (isPassword()) {
-            return mQuality == PASSWORD_QUALITY_NUMERIC
-                    ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD;
+            return StorageManager.CRYPT_TYPE_PASSWORD;
         }
         throw new IllegalStateException("Unhandled credential type");
     }
@@ -219,7 +203,13 @@
         return mType == CREDENTIAL_TYPE_PATTERN;
     }
 
-    /** Returns whether this is a password credential */
+    /** Returns whether this is a numeric pin credential */
+    public boolean isPin() {
+        ensureNotZeroized();
+        return mType == CREDENTIAL_TYPE_PIN;
+    }
+
+    /** Returns whether this is an alphabetic password credential */
     public boolean isPassword() {
         ensureNotZeroized();
         return mType == CREDENTIAL_TYPE_PASSWORD;
@@ -233,7 +223,7 @@
 
     /** Create a copy of the credential */
     public LockscreenCredential duplicate() {
-        return new LockscreenCredential(mType, mQuality,
+        return new LockscreenCredential(mType,
                 mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
     }
 
@@ -263,7 +253,7 @@
             }
             return;
         }
-        if (isPassword()) {
+        if (isPassword() || isPin()) {
             if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
                 throw new IllegalArgumentException("password must not be null and at least "
                         + "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
@@ -272,10 +262,22 @@
         }
     }
 
+    /**
+     * Check if this credential's type matches one that's retrieved from disk. The nuance here is
+     * that the framework used to not distinguish between PIN and password, so this method will
+     * allow a PIN/Password LockscreenCredential to match against the legacy
+     * {@link #CREDENTIAL_TYPE_PASSWORD_OR_PIN} stored on disk.
+     */
+    public boolean checkAgainstStoredType(int storedCredentialType) {
+        if (storedCredentialType == CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+            return getType() == CREDENTIAL_TYPE_PASSWORD || getType() == CREDENTIAL_TYPE_PIN;
+        }
+        return getType() == storedCredentialType;
+    }
+
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mType);
-        dest.writeInt(mQuality);
         dest.writeByteArray(mCredential);
     }
 
@@ -284,8 +286,7 @@
 
         @Override
         public LockscreenCredential createFromParcel(Parcel source) {
-            return new LockscreenCredential(source.readInt(), source.readInt(),
-                    source.createByteArray());
+            return new LockscreenCredential(source.readInt(), source.createByteArray());
         }
 
         @Override
@@ -307,7 +308,7 @@
     @Override
     public int hashCode() {
         // Effective Java — Item 9
-        return ((17 + mType) * 31 + mQuality) * 31 + mCredential.hashCode();
+        return (17 + mType) * 31 + mCredential.hashCode();
     }
 
     @Override
@@ -315,8 +316,7 @@
         if (o == this) return true;
         if (!(o instanceof LockscreenCredential)) return false;
         final LockscreenCredential other = (LockscreenCredential) o;
-        return mType == other.mType && mQuality == other.mQuality
-                && Arrays.equals(mCredential, other.mCredential);
+        return mType == other.mType && Arrays.equals(mCredential, other.mCredential);
     }
 
     /**
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index ea0389f..b752396 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -26,6 +26,7 @@
 import android.os.Environment;
 import android.os.Process;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.storage.StorageManager;
 import android.permission.PermissionManager.SplitPermissionInfo;
 import android.text.TextUtils;
@@ -33,6 +34,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimingsTraceLog;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -411,7 +413,13 @@
     }
 
     SystemConfig() {
-        readAllPermissions();
+        TimingsTraceLog log = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+        log.traceBegin("readAllPermissions");
+        try {
+            readAllPermissions();
+        } finally {
+            log.traceEnd();
+        }
     }
 
     private void readAllPermissions() {
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a568c13..f7d4b3f 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -853,6 +853,7 @@
         optional SettingProto low_battery_sounds_enabled = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto trusted = 13;
         optional SettingProto unlock = 14;
+        optional SettingProto wireless_charging_started = 15;
     }
     optional Sounds sounds = 110;
 
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index d5528de..4ea574d 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -211,6 +211,11 @@
         optional SettingProto silence_timer_touch_count = 11 [ (android.privacy).dest =
             DEST_AUTOMATIC ];
         optional SettingProto skip_touch_count = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto aware_tap_pause_gesture_count = 13 [
+            (android.privacy).dest =
+            DEST_AUTOMATIC ];
+        optional SettingProto aware_tap_pause_touch_count = 14 [ (android.privacy).dest =
+            DEST_AUTOMATIC ];
     }
     optional Gesture gesture = 74;
 
diff --git a/core/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto
new file mode 100644
index 0000000..148bd7e
--- /dev/null
+++ b/core/proto/android/server/notificationhistory.proto
@@ -0,0 +1,89 @@
+/*
+ * 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 com.android.server.notification;
+
+import "frameworks/base/core/proto/android/server/enums.proto";
+
+option java_multiple_files = true;
+
+// On disk data store for historical notifications
+message NotificationHistoryProto {
+  message StringPool {
+    optional int32 size = 1;
+    repeated string strings = 2;
+  }
+
+  message Notification {
+    // The package that posted the notification
+    optional string package = 1;
+    // package_index contains the index + 1 of the package name in the string pool
+    optional int32 package_index = 2;
+
+    // The name of the NotificationChannel this notification was posted to
+    optional string channel_name = 3;
+    // channel_name_index contains the index + 1 of the channel name in the string pool
+    optional int32 channel_name_index = 4;
+
+    // The id of the NotificationChannel this notification was posted to
+    optional string channel_id = 5;
+    // channel_id_index contains the index + 1 of the channel id in the string pool
+    optional int32 channel_id_index = 6;
+
+    // The uid of the package that posted the notification
+    optional int32 uid = 7;
+    // The user id of the package that posted the notification
+    optional int32 user_id = 8;
+    // The time at which the notification was posted
+    optional int64 posted_time_ms = 9;
+    // The title of the notification
+    optional string title = 10;
+    // The text of the notification
+    optional string text = 11;
+    // The small icon of the notification
+    optional Icon icon = 12;
+
+    // Matches the constants of android.graphics.drawable.Icon
+    enum ImageTypeEnum {
+      TYPE_UNKNOWN = 0;
+      TYPE_BITMAP = 1;
+      TYPE_RESOURCE = 2;
+      TYPE_DATA = 3;
+      TYPE_URI = 4;
+      TYPE_ADAPTIVE_BITMAP = 5;
+    }
+
+    message Icon {
+      optional ImageTypeEnum image_type = 1;
+      optional string image_bitmap_filename = 2;
+      optional int32 image_resource_id = 3;
+      optional bytes image_data = 4;
+      optional string image_uri = 5;
+    }
+  }
+
+  // The time the last entry was written
+  optional int64 end_time_ms = 1;
+  // Pool of strings to save space
+  optional StringPool stringpool = 2;
+  // Versioning fields
+  optional int32 major_version = 3;
+  optional int32 minor_version = 4;
+
+  // List of historical notifications
+  repeated Notification notification = 5;
+}
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index 75f265e..f26eefa 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -114,6 +114,4 @@
   repeated UsageStats packages = 20;
   repeated Configuration configurations = 21;
   repeated Event event_log = 22;
-
-  repeated Event pending_events = 23; // TODO: move to usagestatsservice_v2.proto
 }
diff --git a/core/proto/android/server/usagestatsservice_v2.proto b/core/proto/android/server/usagestatsservice_v2.proto
new file mode 100644
index 0000000..a28fcf3
--- /dev/null
+++ b/core/proto/android/server/usagestatsservice_v2.proto
@@ -0,0 +1,135 @@
+/*
+ * 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 com.android.server.usage;
+import "frameworks/base/core/proto/android/content/configuration.proto";
+import "frameworks/base/core/proto/android/privacy.proto";
+
+option java_multiple_files = true;
+
+/**
+ * Obfuscated version of android.service.IntervalStatsProto (usagestatsservice.proto).
+ */
+message IntervalStatsObfuscatedProto {
+
+  message CountAndTime {
+    optional int32 count = 1;
+    optional int64 time_ms = 2;
+  }
+
+  // Stores the relevant information an IntervalStats will have about a Configuration
+  message Configuration {
+    optional .android.content.ConfigurationProto config = 1;
+    optional int64 last_time_active_ms = 2;
+    optional int64 total_time_active_ms = 3;
+    optional int32 count = 4;
+    optional bool active = 5;
+  }
+
+  // The following fields contain supplemental data used to build IntervalStats.
+  optional int64 end_time_ms = 1;
+  optional int32 major_version = 2;
+  optional int32 minor_version = 3;
+
+  // The following fields contain aggregated usage stats data
+  optional CountAndTime interactive = 10;
+  optional CountAndTime non_interactive = 11;
+  optional CountAndTime keyguard_shown = 12;
+  optional CountAndTime keyguard_hidden = 13;
+
+  // The following fields contain listed usage stats data
+  repeated UsageStatsObfuscatedProto packages = 20;
+  repeated Configuration configurations = 21;
+  repeated EventObfuscatedProto event_log = 22;
+  // The following field is only used to persist the reported events before a user unlock
+  repeated PendingEventProto pending_events = 23;
+}
+
+/**
+ * Stores the relevant information from an obfuscated UsageStats.
+ */
+message UsageStatsObfuscatedProto {
+  message ChooserAction {
+    message CategoryCount {
+      optional int32 category_token = 1;
+      optional int32 count = 2;
+    }
+    optional int32 action_token = 1;
+    repeated CategoryCount counts = 2;
+  }
+  optional int32 package_token = 1;
+  optional int64 last_time_active_ms = 3;
+  optional int64 total_time_active_ms = 4;
+  optional int32 last_event = 5;
+  optional int32 app_launch_count = 6;
+  repeated ChooserAction chooser_actions = 7;
+  optional int64 last_time_service_used_ms = 8;
+  optional int64 total_time_service_used_ms = 9;
+  optional int64 last_time_visible_ms = 10;
+  optional int64 total_time_visible_ms = 11;
+}
+
+/**
+ * Stores the relevant information from an obfuscated Event.
+ */
+message EventObfuscatedProto {
+  optional int32 package_token = 1;
+  optional int32 class_token = 2;
+  optional int64 time_ms = 3;
+  optional int32 flags = 4;
+  optional int32 type = 5;
+  optional .android.content.ConfigurationProto config = 6;
+  optional int32 shortcut_id_token = 7;
+  optional int32 standby_bucket = 8;
+  optional int32 notification_channel_id_token = 9;
+  optional int32 instance_id = 10;
+  optional int32 task_root_package_token = 11;
+  optional int32 task_root_class_token = 12;
+}
+
+/**
+ * This message stores all of the fields in an Event object as strings instead of tokens.
+ */
+message PendingEventProto {
+  optional string package_name = 1;
+  optional string class_name = 2;
+  optional int64 time_ms = 3;
+  optional int32 flags = 4;
+  optional int32 type = 5;
+  optional .android.content.ConfigurationProto config = 6;
+  optional string shortcut_id = 7;
+  optional int32 standby_bucket = 8;
+  optional string notification_channel_id = 9;
+  optional int32 instance_id = 10;
+  optional string task_root_package = 11;
+  optional string task_root_class = 12;
+}
+
+/**
+ * A proto message representing the obfuscated tokens mappings for Usage Stats.
+ */
+message ObfuscatedPackagesProto {
+  message PackagesMap {
+    optional int32 package_token = 1;
+    // The list of strings for each package where their indices are the token
+    repeated string strings = 2;
+  }
+
+  optional int32 counter = 1;
+  // Stores the mappings for every package
+  repeated PackagesMap packages_map = 2;
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 75cdb42..eaf93eb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4357,4 +4357,8 @@
          create additional screen real estate outside beyond the keyboard. Note that the user needs
          to have a confirmed way to dismiss the keyboard when desired. -->
     <bool name="config_automotiveHideNavBarForKeyboard">false</bool>
+
+    <!-- Whether or not to show the built-in charging animation when the device begins charging
+         wirelessly. -->
+    <bool name="config_showBuiltinWirelessChargingAnim">true</bool>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a01bbe3..9791241 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -41,6 +41,8 @@
     <dimen name="quick_qs_offset_height">48dp</dimen>
     <!-- Total height of QQS (quick_qs_offset_height + 128) -->
     <dimen name="quick_qs_total_height">176dp</dimen>
+    <!-- Total height of QQS with two rows to fit media player (quick_qs_offset_height + 176) -->
+    <dimen name="quick_qs_total_height_with_media">224dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_height">48dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index bf5f706..d39d507 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3440,6 +3440,15 @@
     <!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
     <string name="wifi_no_internet_detailed">Tap for options</string>
 
+    <!-- A notification is shown when the user connects to a mobile network without internet access. This is the notification's title. -->
+    <string name="mobile_no_internet">Mobile network has no internet access</string>
+
+    <!-- A notification is shown when the user connects to a non-mobile and non-wifi network without internet access. This is the notification's title. -->
+    <string name="other_networks_no_internet">Network has no internet access</string>
+
+    <!-- A notification is shown when connected network without internet due to private dns validation failed. This is the notification's message. [CHAR LIMIT=NONE] -->
+    <string name="private_dns_broken_detailed">Private DNS server cannot be accessed</string>
+
     <!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] -->
     <string name="captive_portal_logged_in_detailed">Connected</string>
     <!-- A notification is shown when the user connects to a network that doesn't have access to some services (e.g. Push notifications may not work). This is the notification's title. [CHAR LIMIT=50] -->
@@ -5205,6 +5214,8 @@
     <string name="autofill_save_no">No thanks</string>
     <!-- Label for the autofill update button [CHAR LIMIT=NONE] -->
     <string name="autofill_update_yes">Update</string>
+    <!-- Label for the autofill continue button [CHAR LIMIT=NONE] -->
+    <string name="autofill_continue_yes">Continue</string>
 
     <!-- Label for the type of data being saved for autofill when it represent user credentials with a password [CHAR LIMIT=NONE] -->
     <string name="autofill_save_type_password">password</string>
@@ -5212,6 +5223,12 @@
     <string name="autofill_save_type_address">address</string>
     <!-- Label for the type of data being saved for autofill when it represents a credit card [CHAR LIMIT=NONE] -->
     <string name="autofill_save_type_credit_card">credit card</string>
+    <!-- Label for the type of data being saved for autofill when it represents a debit card [CHAR LIMIT=NONE] -->
+    <string name="autofill_save_type_debit_card">debit card</string>
+    <!-- Label for the type of data being saved for autofill when it represents a payment card [CHAR LIMIT=NONE] -->
+    <string name="autofill_save_type_payment_card">payment card</string>
+    <!-- Label for the type of data being saved for autofill when it represents a card that does not a specified type or cannot identify what the type is for [CHAR LIMIT=NONE] -->
+    <string name="autofill_save_type_generic_card">card</string>
     <!-- Label for the type of data being saved for autofill when it represents an username [CHAR LIMIT=NONE] -->
     <string name="autofill_save_type_username">username</string>
     <!-- Label for the type of data being saved for autofill when it represents an email address [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6bb4719..8d96c79 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -593,9 +593,12 @@
   <java-symbol type="string" name="menu_space_shortcut_label" />
   <java-symbol type="string" name="menu_shift_shortcut_label" />
   <java-symbol type="string" name="menu_sym_shortcut_label" />
+  <java-symbol type="string" name="mobile_no_internet" />
   <java-symbol type="string" name="notification_title" />
+  <java-symbol type="string" name="other_networks_no_internet" />
   <java-symbol type="string" name="permission_request_notification_with_subtitle" />
   <java-symbol type="string" name="prepend_shortcut_label" />
+  <java-symbol type="string" name="private_dns_broken_detailed" />
   <java-symbol type="string" name="paste_as_plain_text" />
   <java-symbol type="string" name="replace" />
   <java-symbol type="string" name="undo" />
@@ -1774,6 +1777,7 @@
   <java-symbol type="dimen" name="display_cutout_touchable_region_size" />
   <java-symbol type="dimen" name="quick_qs_offset_height" />
   <java-symbol type="dimen" name="quick_qs_total_height" />
+  <java-symbol type="dimen" name="quick_qs_total_height_with_media" />
   <java-symbol type="drawable" name="ic_jog_dial_sound_off" />
   <java-symbol type="drawable" name="ic_jog_dial_sound_on" />
   <java-symbol type="drawable" name="ic_jog_dial_unlock" />
@@ -3355,6 +3359,7 @@
   <java-symbol type="string" name="autofill_update_title_with_2types" />
   <java-symbol type="string" name="autofill_update_title_with_3types" />
   <java-symbol type="string" name="autofill_update_yes" />
+  <java-symbol type="string" name="autofill_continue_yes" />
   <java-symbol type="string" name="autofill_save_accessibility_title " />
   <java-symbol type="string" name="autofill_save_title" />
   <java-symbol type="string" name="autofill_save_title_with_type" />
@@ -3365,6 +3370,9 @@
   <java-symbol type="string" name="autofill_save_type_password" />
   <java-symbol type="string" name="autofill_save_type_address" />
   <java-symbol type="string" name="autofill_save_type_credit_card" />
+  <java-symbol type="string" name="autofill_save_type_debit_card" />
+  <java-symbol type="string" name="autofill_save_type_payment_card" />
+  <java-symbol type="string" name="autofill_save_type_generic_card" />
   <java-symbol type="string" name="autofill_save_type_username" />
   <java-symbol type="string" name="autofill_save_type_email_address" />
   <java-symbol type="drawable" name="autofill_dataset_picker_background" />
@@ -3866,4 +3874,6 @@
 
   <java-symbol type="bool" name="config_automotiveHideNavBarForKeyboard" />
 
+  <java-symbol type="bool" name="config_showBuiltinWirelessChargingAnim" />
+
 </resources>
diff --git a/core/tests/PlatformCompatFramework/Android.bp b/core/tests/PlatformCompatFramework/Android.bp
new file mode 100644
index 0000000..3380265
--- /dev/null
+++ b/core/tests/PlatformCompatFramework/Android.bp
@@ -0,0 +1,14 @@
+android_test {
+    name: "PlatformCompatFrameworkTests",
+    // Include all test java files.
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "junit",
+        "androidx.test.rules",
+    ],
+    platform_apis: true,
+}
diff --git a/core/tests/PlatformCompatFramework/AndroidManifest.xml b/core/tests/PlatformCompatFramework/AndroidManifest.xml
new file mode 100644
index 0000000..4a6383b
--- /dev/null
+++ b/core/tests/PlatformCompatFramework/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.internal.compat">
+    <application android:label="ChangeReporterTest">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.internal.compat"/>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/PlatformCompatFramework/OWNERS b/core/tests/PlatformCompatFramework/OWNERS
new file mode 100644
index 0000000..2b7cdb0
--- /dev/null
+++ b/core/tests/PlatformCompatFramework/OWNERS
@@ -0,0 +1,7 @@
+# Use this reviewer by default.
+platform-compat-eng+reviews@google.com
+
+andreionea@google.com
+atrost@google.com
+mathewi@google.com
+satayev@google.com
diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
new file mode 100644
index 0000000..09bbe37
--- /dev/null
+++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class ChangeReporterTest {
+    @Test
+    public void testStatsLogOnce() {
+        ChangeReporter reporter = new ChangeReporter(0);
+        int myUid = 1022, otherUid = 1023;
+        long myChangeId = 500L, otherChangeId = 600L;
+        int myState = 1, otherState = 2;
+
+        assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+        reporter.reportChange(myUid, myChangeId, myState);
+
+        // Same report will not be logged again.
+        assertFalse(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+        // Other reports will be logged.
+        assertTrue(reporter.shouldWriteToStatsLog(otherUid, myChangeId, myState));
+        assertTrue(reporter.shouldWriteToStatsLog(myUid, otherChangeId, myState));
+        assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, otherState));
+    }
+
+    @Test
+    public void testStatsLogAfterReset() {
+        ChangeReporter reporter = new ChangeReporter(0);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myState = 1;
+
+        assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+        reporter.reportChange(myUid, myChangeId, myState);
+
+        // Same report will not be logged again.
+        assertFalse(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+        reporter.resetReportedChanges(myUid);
+
+        // Same report will be logged again after reset.
+        assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+    }
+
+    @Test
+    public void testDebugLogOnce() {
+        ChangeReporter reporter = new ChangeReporter(0);
+        int myUid = 1022, otherUid = 1023;
+        long myChangeId = 500L, otherChangeId = 600L;
+        int myState = 1, otherState = 2;
+
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
+        reporter.reportChange(myUid, myChangeId, myState);
+
+        // Same report will not be logged again.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
+        // Other reports will be logged.
+        assertTrue(reporter.shouldWriteToDebug(otherUid, myChangeId, myState));
+        assertTrue(reporter.shouldWriteToDebug(myUid, otherChangeId, myState));
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, otherState));
+    }
+
+    @Test
+    public void testDebugLogAfterReset() {
+        ChangeReporter reporter = new ChangeReporter(0);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myState = 1;
+
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
+        reporter.reportChange(myUid, myChangeId, myState);
+
+        // Same report will not be logged again.
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
+        reporter.resetReportedChanges(myUid);
+
+        // Same report will be logged again after reset.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
+    }
+
+    @Test
+    public void testDebugLogWithLogAll() {
+        ChangeReporter reporter = new ChangeReporter(0);
+        int myUid = 1022;
+        long myChangeId = 500L;
+        int myState = 1;
+
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
+        reporter.reportChange(myUid, myChangeId, myState);
+
+        reporter.startDebugLogAll();
+        // Same report will be logged again.
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
+        assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
+
+        reporter.stopDebugLogAll();
+        assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
+    }
+}
diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
new file mode 100644
index 0000000..1b5ad88
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.timedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.TimestampedValue;
+
+import org.junit.Test;
+
+public class PhoneTimeSuggestionTest {
+    private static final int PHONE_ID = 99999;
+
+    @Test
+    public void testEquals() {
+        PhoneTimeSuggestion one =
+                new PhoneTimeSuggestion(PHONE_ID, new TimestampedValue<>(1111L, 2222L));
+        assertEquals(one, one);
+
+        PhoneTimeSuggestion two =
+                new PhoneTimeSuggestion(PHONE_ID, new TimestampedValue<>(1111L, 2222L));
+        assertEquals(one, two);
+        assertEquals(two, one);
+
+        PhoneTimeSuggestion three =
+                new PhoneTimeSuggestion(PHONE_ID + 1, new TimestampedValue<>(1111L, 2222L));
+        assertNotEquals(one, three);
+        assertNotEquals(three, one);
+
+        // DebugInfo must not be considered in equals().
+        one.addDebugInfo("Debug info 1");
+        two.addDebugInfo("Debug info 2");
+        assertEquals(one, two);
+    }
+
+    @Test
+    public void testParcelable() {
+        PhoneTimeSuggestion one =
+                new PhoneTimeSuggestion(PHONE_ID, new TimestampedValue<>(1111L, 2222L));
+        assertEquals(one, roundTripParcelable(one));
+
+        // DebugInfo should also be stored (but is not checked by equals()
+        one.addDebugInfo("This is debug info");
+        PhoneTimeSuggestion two = roundTripParcelable(one);
+        assertEquals(one.getDebugInfo(), two.getDebugInfo());
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T extends Parcelable> T roundTripParcelable(T one) {
+        Parcel parcel = Parcel.obtain();
+        parcel.writeTypedObject(one, 0);
+        parcel.setDataPosition(0);
+
+        T toReturn = (T) parcel.readTypedObject(PhoneTimeSuggestion.CREATOR);
+        parcel.recycle();
+        return toReturn;
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
index 5eec91c..05bab1c 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -31,10 +31,23 @@
         assertEquals(0, empty.size());
         assertNotNull(empty.getCredential());
 
+        assertFalse(empty.isPin());
         assertFalse(empty.isPassword());
         assertFalse(empty.isPattern());
     }
 
+    public void testPinCredential() {
+        LockscreenCredential pin = LockscreenCredential.createPin("3456");
+
+        assertTrue(pin.isPin());
+        assertEquals(4, pin.size());
+        assertTrue(Arrays.equals("3456".getBytes(), pin.getCredential()));
+
+        assertFalse(pin.isNone());
+        assertFalse(pin.isPassword());
+        assertFalse(pin.isPattern());
+    }
+
     public void testPasswordCredential() {
         LockscreenCredential password = LockscreenCredential.createPassword("password");
 
@@ -43,6 +56,7 @@
         assertTrue(Arrays.equals("password".getBytes(), password.getCredential()));
 
         assertFalse(password.isNone());
+        assertFalse(password.isPin());
         assertFalse(password.isPattern());
     }
 
@@ -60,6 +74,7 @@
         assertTrue(Arrays.equals("12369".getBytes(), pattern.getCredential()));
 
         assertFalse(pattern.isNone());
+        assertFalse(pattern.isPin());
         assertFalse(pattern.isPassword());
     }
 
@@ -72,6 +87,15 @@
                 LockscreenCredential.createPasswordOrNone("abcd"));
     }
 
+    public void testPinOrNoneCredential() {
+        assertEquals(LockscreenCredential.createNone(),
+                LockscreenCredential.createPinOrNone(null));
+        assertEquals(LockscreenCredential.createNone(),
+                LockscreenCredential.createPinOrNone(""));
+        assertEquals(LockscreenCredential.createPin("1357"),
+                LockscreenCredential.createPinOrNone("1357"));
+    }
+
     public void testSanitize() {
         LockscreenCredential password = LockscreenCredential.createPassword("password");
         password.zeroize();
@@ -80,12 +104,15 @@
             password.isNone();
             fail("Sanitized credential still accessible");
         } catch (IllegalStateException expected) { }
-
         try {
             password.isPattern();
             fail("Sanitized credential still accessible");
         } catch (IllegalStateException expected) { }
         try {
+            password.isPin();
+            fail("Sanitized credential still accessible");
+        } catch (IllegalStateException expected) { }
+        try {
             password.isPassword();
             fail("Sanitized credential still accessible");
         } catch (IllegalStateException expected) { }
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 9913531..50e8474 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -62,7 +62,9 @@
         Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
 
         final ILockSettings ils = Mockito.mock(ILockSettings.class);
-        when(ils.havePassword(DEMO_USER_ID)).thenReturn(isSecure);
+        when(ils.getCredentialType(DEMO_USER_ID)).thenReturn(
+                isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                         : LockPatternUtils.CREDENTIAL_TYPE_NONE);
         when(ils.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED, DEMO_USER_ID))
                 .thenReturn((long) PASSWORD_QUALITY_MANAGED);
         // TODO(b/63758238): stop spying the class under test
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 8508f7c..012ffcc 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1,6 +1,36 @@
 {
   "version": "1.0.0",
   "messages": {
+    "-2146181682": {
+      "message": "Releasing screen wakelock, obscured by %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_KEEP_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-2138637148": {
+      "message": "Clearing focused app, displayId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/ActivityDisplay.java"
+    },
+    "-2127842445": {
+      "message": "Clearing startingData for token=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-2109936758": {
+      "message": "removeAppToken make exiting: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-2109864870": {
+      "message": "app-release(): mOuter=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
     "-2072089308": {
       "message": "Attempted to add window with token that is a sub-window: %s.  Aborting.",
       "level": "WARN",
@@ -13,60 +43,288 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-2024464438": {
+      "message": "app-onAnimationFinished(): mOuter=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "-2012562539": {
+      "message": "startAnimation(): Notify animation start:",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "-2002500255": {
+      "message": "Defer removing snapshot surface in %dms",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java"
+    },
+    "-1991255017": {
+      "message": "Drawing snapshot surface sizeMismatch=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java"
+    },
     "-1976930686": {
       "message": "Attempted to add Accessibility overlay window with bad token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1963461591": {
+      "message": "Removing %s from %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-1958209312": {
+      "message": "Clear freezing of %s: hidden=%b freezing=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1953668890": {
+      "message": "Can't start recents animation, nextAppTransition=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
     "-1949279037": {
       "message": "Attempted to add input method window with bad token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1939358269": {
+      "message": "mRecentScreenshotAnimator finish",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
+    "-1938839202": {
+      "message": "SURFACE LEAK DESTROY: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "-1938204785": {
+      "message": "Moving existing starting %s from %s to %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1915280162": {
       "message": "Attempted to add wallpaper window with bad token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1884933373": {
+      "message": "enableScreenAfterBoot: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_BOOT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-1878839956": {
+      "message": "Marking app token %s with replacing windows.",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1872288685": {
+      "message": "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "-1868124841": {
+      "message": "screenOnEarly=%b, awake=%b, currentAppOrientation=%d, orientationSensorEnabled=%b, keyguardDrawComplete=%b, windowManagerDrawComplete=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "-1862269827": {
+      "message": "applyAnimation: anim=%s transit=%s isEntrance=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "-1847087163": {
+      "message": "TRANSIT_TASK_OPEN_BEHIND,  adding %s to mOpeningApps",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1838803135": {
       "message": "Attempted to set windowing mode to a display that does not exist: %d",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1824578273": {
+      "message": "Reporting new frame to %s: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_RESIZE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "-1822611824": {
+      "message": "\tRemove token=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "-1797409732": {
+      "message": "Skipping %s because %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "-1782453012": {
+      "message": "Checking theme of starting window: 0x%x",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1770075711": {
       "message": "Adding window client %s that is dead, aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1768557332": {
+      "message": "removeWallpaperAnimation()",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
     "-1750206390": {
       "message": "Exception thrown when creating surface for client %s (%s). %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1747461042": {
+      "message": "set mOrientationChanging of %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "-1741065110": {
+      "message": "No app is requesting an orientation, return %d for display id=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "-1730156332": {
+      "message": "Display id=%d rotation changed to %d from %d, lastOrientation=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "-1715268616": {
+      "message": "Last window, removing starting window %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1710206702": {
+      "message": "Display id=%d is frozen while keyguard locked, return %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "-1698815688": {
+      "message": "Resetting app token %s of replacing window marks.",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1661704580": {
       "message": "Attempted to set replacing window on non-existing app token %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1632122349": {
+      "message": "Changing surface while display frozen: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-1596995693": {
+      "message": "startAnimation",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "-1587841219": {
+      "message": "Focus moving from %s to %s displayId=%d",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-1568331821": {
+      "message": "Enabling listeners",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
     "-1545962566": {
       "message": "View server did not start",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1539974875": {
+      "message": "removeAppToken: %s delayed=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1526645239": {
       "message": "Timeout waiting for drawn: undrawn=%s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1515151503": {
+      "message": ">>> OPEN TRANSACTION removeReplacedWindows",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
+    "-1499134947": {
+      "message": "Removing starting %s from %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1497837552": {
+      "message": "onAnimationFinished(): mPendingAnimations=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "-1483752006": {
+      "message": "  THUMBNAIL %s: CREATE",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/AppWindowThumbnail.java"
+    },
+    "-1471946192": {
+      "message": "Marking app token %s with replacing child windows.",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1456549051": {
+      "message": "setClientHidden: %s clientHidden=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1455600136": {
       "message": "Attempted to add Dream window with unknown token %s.  Aborting.",
       "level": "WARN",
@@ -79,42 +337,246 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1434147454": {
+      "message": "cleanupAnimation(): Notify animation finished mPendingAnimations=%d reorderMode=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
+    "-1427184084": {
+      "message": "addWindow: New client %s: window=%s Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-1413901262": {
+      "message": "startRecentsActivity(): intent=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "-1391944764": {
+      "message": "SURFACE DESTROY: %s. %s",
+      "level": "INFO",
+      "group": "WM_SHOW_SURFACE_ALLOC",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
     "-1389772804": {
       "message": "Attempted to add voice interaction window with bad token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1352076759": {
+      "message": "Removing app token: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1350198040": {
+      "message": "hideBootMessagesLocked: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_BOOT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-1340540100": {
+      "message": "Creating SnapshotStartingData",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1292329638": {
+      "message": "Added starting %s: startingWindow=%s startingView=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1270731689": {
       "message": "Attempted to set replacing window on app token with no content %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1270148832": {
+      "message": "Resize start waiting for draw, mDrawState=DRAW_PENDING in %s, surfaceController %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_RESIZE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-1263554915": {
       "message": "Attempted to add Dream window with bad token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1259022216": {
+      "message": "SURFACE HIDE ( %s ): %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "-1257821162": {
+      "message": "OUT SURFACE %s: copied",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-1219773477": {
+      "message": "setInputConsumerEnabled(%s): mCanceled=%b",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
+    "-1207757583": {
+      "message": "startAnimation(): Notify animation start: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
+    "-1176488860": {
+      "message": "SURFACE isSecure=%b: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "-1156118957": {
+      "message": "Updated config=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "-1144293044": {
+      "message": "SURFACE SET FREEZE LAYER: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "-1130891072": {
+      "message": "Orientation continue waiting for draw in %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "-1130868271": {
+      "message": "Resizing %s WITH DRAW PENDING",
+      "level": "INFO",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "-1128015008": {
+      "message": "Schedule remove starting %s startingWindow=%s startingView=%s Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1117599386": {
+      "message": "Deferring rotation, display is not enabled.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
     "-1113134997": {
       "message": "Attempted to add application window with unknown token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1103716954": {
+      "message": "Not removing %s due to exit animation",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "-1103115659": {
+      "message": "Performing post-rotate rotation",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
+    "-1099052739": {
+      "message": "\tAdd token=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "-1089874824": {
+      "message": "SURFACE SHOW (performLayout): %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "-1088782910": {
+      "message": "Translucent=%s Floating=%s ShowWallpaper=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-1076978367": {
+      "message": "thawRotation: mRotation=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-1060365734": {
       "message": "Attempted to add QS dialog window with bad token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1047945589": {
+      "message": "Remove client=%x, surfaceController=%s Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "-1044506655": {
+      "message": "New transit away from wallpaper: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "-1042574499": {
       "message": "Attempted to add Accessibility overlay window with unknown token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1009117329": {
+      "message": "isFetchingAppTransitionSpecs=true",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "-993378225": {
+      "message": "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_DRAW",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "-986746907": {
+      "message": "Starting window removed %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "-931184679": {
+      "message": "Changing app %s hidden=%b performLayout=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-928291778": {
+      "message": "applyAnimation: anim=%s nextAppTransition=%d transit=%s Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "-916108501": {
+      "message": "Adding %s to %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "-914253865": {
       "message": "Attempted to add voice interaction window with unknown token %s.  Aborting.",
       "level": "WARN",
@@ -127,36 +589,186 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-874446906": {
+      "message": "showBootMessage: msg=%s always=%b mAllowBootMessages=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_BOOT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-861859917": {
       "message": "Attempted to add window to a display that does not exist: %d. Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-861707633": {
+      "message": "Destroying surface %s called by %s",
+      "level": "INFO",
+      "group": "WM_SHOW_SURFACE_ALLOC",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "-856025122": {
+      "message": "SURFACE transparentRegionHint=%s: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-853404763": {
+      "message": "\twallpaper=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
     "-853226675": {
       "message": "Attempted to add window with exiting application token .%s Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-809771899": {
+      "message": "findFocusedWindow: Reached focused app=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "-793346159": {
+      "message": "New transit into wallpaper: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "-784959154": {
       "message": "Attempted to add private presentation window to a non-private display.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-783405930": {
+      "message": "Performing post-rotate rotation",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-771282525": {
+      "message": "Losing focus: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-771177730": {
+      "message": "Removing focused app token:%s displayId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-760801764": {
+      "message": "onAnimationCancelled",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
+    },
+    "-754503024": {
+      "message": "Relayout %s: oldVis=%d newVis=%d. %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-747671114": {
       "message": "Failed looking up window callers=%s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-714291355": {
+      "message": "Losing delayed focus: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-694710814": {
+      "message": "Pausing rotation during drag",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DragState.java"
+    },
+    "-687185281": {
+      "message": "New topFocusedDisplayId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
+    "-666510420": {
+      "message": "With display frozen, orientationChangeComplete=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
+    "-653156702": {
+      "message": "createAppAnimations()",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "-650040763": {
+      "message": "rotationForOrientation(orient=%d, last=%d); user=%d %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "-635082269": {
+      "message": "******** booted=%b msg=%b haveBoot=%b haveApp=%b haveWall=%b wallEnabled=%b haveKeyguard=%b",
+      "level": "INFO",
+      "group": "WM_DEBUG_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "-622997754": {
+      "message": "postWindowRemoveCleanupLocked: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-618015844": {
+      "message": "performEnableScreen: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b mOnlyCore=%b. %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_BOOT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-583031528": {
       "message": "%s",
       "level": "INFO",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-576070986": {
+      "message": "Performing post-rotate rotation after seamless rotation",
+      "level": "INFO",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "-554834595": {
+      "message": "Display id=%d is frozen, return %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "-549028919": {
+      "message": "enableScreenIfNeededLocked: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_BOOT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-545190927": {
+      "message": "<<< CLOSE TRANSACTION animate",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowAnimator.java"
+    },
+    "-519504830": {
+      "message": "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s isEntrance=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
     "-507657818": {
       "message": "Window %s is already added",
       "level": "WARN",
@@ -169,6 +781,60 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-481924678": {
+      "message": "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w.isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_KEEP_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
+    "-477481651": {
+      "message": "SURFACE DESTROY PENDING: %s. %s",
+      "level": "INFO",
+      "group": "WM_SHOW_SURFACE_ALLOC",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "-445944810": {
+      "message": "finish(%b): mCanceled=%b",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
+    "-444624452": {
+      "message": "REPARENT from: %s to: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "-443173857": {
+      "message": "Moving pending starting from %s to %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-439951996": {
+      "message": "Disabling listeners",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "-415912575": {
+      "message": "setTask: %s at top.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-415865166": {
+      "message": "findFocusedWindow: Found new focus @ %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "-405536909": {
+      "message": "Removing snapshot surface",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/TaskSnapshotSurface.java"
+    },
     "-393505149": {
       "message": "unable to update pointer icon",
       "level": "WARN",
@@ -181,42 +847,192 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-379068494": {
+      "message": "unknownApps is not empty: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "-371630969": {
+      "message": "New wallpaper target=%s, oldWallpaper=%s, openingApps=%s, closingApps=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "-344488673": {
+      "message": "Finishing drawing window %s: mDrawState=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "-324085783": {
+      "message": "SURFACE CROP %s: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
     "-322035974": {
       "message": "App freeze timeout expired.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-320419645": {
+      "message": "Removing replaced window: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "-253016819": {
+      "message": "applyAnimation: transition animation is disabled or skipped. atoken=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-251259736": {
+      "message": "No longer freezing: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "-198463978": {
+      "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-193782861": {
+      "message": "Final remove of window: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_MOVEMENT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-167822951": {
       "message": "Attempted to add starting window to token with already existing starting window",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-129722369": {
+      "message": "New transit: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "-121104356": {
+      "message": "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b mWillReplaceWindow=%b inPendingTransaction=%b mDisplayFrozen=%b callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "-116086365": {
+      "message": "******************** ENABLING SCREEN!",
+      "level": "INFO",
+      "group": "WM_DEBUG_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-106400104": {
+      "message": "Preload recents with %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "-96848838": {
+      "message": "Gaining focus: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "-87705714": {
+      "message": "findFocusedWindow: focusedApp=null using new focus @ %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "-87703044": {
       "message": "Boot completed: SurfaceFlinger is dead!",
       "level": "ERROR",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-86763148": {
+      "message": "  KILL SURFACE SESSION %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/Session.java"
+    },
+    "-29233992": {
+      "message": "SURFACE CLEAR CROP: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "-7343917": {
+      "message": "onAnimationFinished(): targetStack=%s targetActivity=%s mRestoreTargetBehindStack=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "9803449": {
+      "message": "startFreezingDisplayLocked: exitAnim=%d enterAnim=%d called by %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "10608884": {
+      "message": "  FREEZE %s: CREATE",
+      "level": "INFO",
+      "group": "WM_SHOW_SURFACE_ALLOC",
+      "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
+    },
     "11060725": {
       "message": "Attempted to get system decors flag of a display that does not exist: %d",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "17696244": {
+      "message": "startAnimation(): mPendingStart=%b mCanceled=%b",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
     "38267433": {
       "message": "Attempted to reset replacing window on non-existing app token %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "44438983": {
+      "message": "performLayout: Activity exiting now removed %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "45285419": {
+      "message": "startingWindow was set but startingSurface==null, couldn't remove",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "51200510": {
+      "message": "  BLACK %s: DESTROY",
+      "level": "INFO",
+      "group": "WM_SHOW_SURFACE_ALLOC",
+      "at": "com\/android\/server\/wm\/BlackFrame.java"
+    },
     "51628177": {
       "message": "Attempted to get windowing mode of a display that does not exist: %d",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "83950285": {
+      "message": "removeAnimation(%d)",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
     "91350919": {
       "message": "Attempted to set IME flag to a display that does not exist: %d",
       "level": "WARN",
@@ -229,24 +1045,174 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "95902367": {
+      "message": "Relayout of %s: focusMayChange=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "100936473": {
+      "message": "Wallpaper animation!",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "108170907": {
+      "message": "Add starting %s: startingData=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "123161180": {
+      "message": "SEVER CHILDREN",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "146871307": {
+      "message": "Tried to remove starting window but startingWindow was null: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "150351993": {
+      "message": "addWindow: %s startingWindow=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "152914409": {
+      "message": "  BLACK %s: CREATE layer=%d",
+      "level": "INFO",
+      "group": "WM_SHOW_SURFACE_ALLOC",
+      "at": "com\/android\/server\/wm\/BlackFrame.java"
+    },
+    "184362060": {
+      "message": "screenshotTask(%d): mCanceled=%b",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
+    "186668272": {
+      "message": "Now changing app %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "194124419": {
+      "message": "goodToGo(): Animation finished already, canceled=%s mPendingAnimations=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "221540118": {
+      "message": "mUserActivityTimeout set to %d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_KEEP_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
+    "241961619": {
+      "message": "Adding %s to %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowToken.java"
+    },
     "246676969": {
       "message": "Attempted to add window with non-application token .%s Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "248210157": {
+      "message": "Finishing remote animation",
+      "level": "INFO",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
     "254883724": {
       "message": "addWindowToken: Attempted to add binder token: %s for already created window token: %s displayId=%d",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "255692476": {
+      "message": "**** GOOD TO GO",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "269576220": {
+      "message": "Resuming rotation after drag",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DragState.java"
+    },
+    "274773837": {
+      "message": "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL transit=%s Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "285317231": {
+      "message": "Input focus has changed to %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/InputMonitor.java"
+    },
     "288485303": {
       "message": "Attempted to set remove mode to a display that does not exist: %d",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "289967521": {
+      "message": "Check opening app=%s: allDrawn=%b startingDisplayed=%b startingMoved=%b isRelaunching()=%b startingWindow=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "292904800": {
+      "message": "Deferring rotation, animation in progress.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "302992539": {
+      "message": "addAnimation(%s)",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
+    "309039362": {
+      "message": "SURFACE MATRIX [%f,%f,%f,%f]: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "342460966": {
+      "message": "DRAG %s: pos=(%d,%d)",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/DragState.java"
+    },
+    "344795667": {
+      "message": "*** APP TRANSITION TIMEOUT. displayId=%d isTransitionSet()=%b mOpeningApps.size()=%d mClosingApps.size()=%d mChangingApps.size()=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "355720268": {
+      "message": "stopFreezingDisplayLocked: Unfreezing now",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "358613119": {
+      "message": "setAppVisibility(%s, visible=%b): %s hidden=%b hiddenRequested=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "371641947": {
       "message": "Window Manager Crash %s",
       "level": "WTF",
@@ -259,6 +1225,36 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "374972436": {
+      "message": "performEnableScreen: Waiting for anim complete",
+      "level": "INFO",
+      "group": "WM_DEBUG_BOOT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "385096046": {
+      "message": "Delaying loss of focus...",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "393054329": {
+      "message": "reParentWindowToken: removing window token=%s from task=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "399841913": {
+      "message": "SURFACE RECOVER DESTROY: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_SURFACE_ALLOC",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
+    "417311568": {
+      "message": "onResize: Resizing %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RESIZE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "424524729": {
       "message": "Attempted to add wallpaper window with unknown token %s.  Aborting.",
       "level": "WARN",
@@ -271,42 +1267,276 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "457951957": {
+      "message": "\tNot visible=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
+    },
+    "463993897": {
+      "message": "Aborted waiting for drawn: %s",
+      "level": "WARN",
+      "group": "WM_DEBUG_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "474000473": {
+      "message": "No stack above target stack=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "481370485": {
+      "message": "Computed rotation=%d for display id=%d based on lastOrientation=%d and oldRotation=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "490877640": {
+      "message": "onStackOrderChanged(): stack=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "495032901": {
+      "message": "Expected target stack=%s to restored behind stack=%s but it is behind stack=%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "508887531": {
+      "message": "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "539077569": {
+      "message": "Clear freezing of %s force=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "557227556": {
+      "message": "onAnimationFinished(): Notify animation finished:",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "558823034": {
+      "message": "SURFACE isOpaque=%b: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "585096182": {
+      "message": "SURFACE isColorSpaceAgnostic=%b: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "594260577": {
+      "message": "createWallpaperAnimations()",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "600140673": {
+      "message": "checkBootAnimationComplete: Waiting for anim complete",
+      "level": "INFO",
+      "group": "WM_DEBUG_BOOT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "608694300": {
+      "message": "  NEW SURFACE SESSION %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/Session.java"
+    },
     "620368427": {
       "message": "******* TELLING SURFACE FLINGER WE ARE BOOTED!",
       "level": "INFO",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "628276090": {
+      "message": "Delaying app transition for screen rotation animation to finish",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "631792420": {
       "message": "Attempted to add window with token that is not a window: %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "633654009": {
+      "message": "SURFACE POS (setPositionInTransaction) @ (%f,%f): %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "644675193": {
+      "message": "Real start recents",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "646155519": {
+      "message": "Started intent=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
     "662572728": {
       "message": "Attempted to add a toast window with bad token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "665256544": {
+      "message": "All windows drawn!",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "676824470": {
       "message": "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
       "level": "ERROR",
       "group": "TEST_GROUP",
       "at": "com\/android\/server\/wm\/ProtoLogGroup.java"
     },
+    "685047360": {
+      "message": "Resizing window %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_RESIZE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "690411811": {
+      "message": "goodToGo(): No apps to animate",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "693423992": {
+      "message": "setAnimationLocked: setting mFocusMayChange true",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "704998117": {
       "message": "Failed to create surface control for %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "745391677": {
+      "message": "  CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x \/ %s",
+      "level": "INFO",
+      "group": "WM_SHOW_SURFACE_ALLOC",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "765395228": {
+      "message": "onAnimationFinished(): controller=%s reorderMode=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "789829331": {
+      "message": "Aborted starting %s: removed=%b startingData=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "791468751": {
+      "message": "Pausing rotation during re-position",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/TaskPositioner.java"
+    },
+    "794570322": {
+      "message": "Now closing app %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "806891543": {
+      "message": "Setting mOrientationChangeComplete=true because wtoken %s numInteresting=%d numDrawn=%d",
+      "level": "INFO",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "815803557": {
+      "message": "applyAnimation: atoken=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "829434921": {
+      "message": "Draw state now committed in %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "835814848": {
+      "message": "%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "845234215": {
+      "message": "App is requesting an orientation, return %d for display id=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "853091290": {
+      "message": "Moved stack=%s behind stack=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
+    "857751535": {
+      "message": "commitVisibility: %s: hidden=%b hiddenRequested=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "873914452": {
+      "message": "goodToGo()",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "892244061": {
+      "message": "Waiting for drawn %s: removed=%b visible=%b mHasSurface=%b drawState=%d",
+      "level": "INFO",
+      "group": "WM_DEBUG_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "898863925": {
       "message": "Attempted to add QS dialog window with unknown token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "913494177": {
+      "message": "removeAllWindowsIfPossible: removing win=%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_WINDOW_MOVEMENT",
+      "at": "com\/android\/server\/wm\/WindowToken.java"
+    },
+    "916191774": {
+      "message": "Orientation change complete in %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "917739349": {
+      "message": "Set focused app to: %s moveFocusNow=%b displayId=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/ActivityDisplay.java"
+    },
     "954470154": {
       "message": "FORCED DISPLAY SCALING DISABLED",
       "level": "INFO",
@@ -319,60 +1549,324 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1004585481": {
+      "message": "%s forcing orientation to %d for display id=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "1051545910": {
+      "message": "Exit animation finished in %s: remove=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "1073230342": {
+      "message": "startAnimation",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
+    },
+    "1089714158": {
+      "message": "  FREEZE %s: DESTROY",
+      "level": "INFO",
+      "group": "WM_SHOW_SURFACE_ALLOC",
+      "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
+    },
+    "1108406230": {
+      "message": "stopFreezingDisplayLocked: Returning mWaitingForConfig=%b, mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, mClientFreezingScreen=%b, mOpeningApps.size()=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "1112047265": {
+      "message": "finishDrawingWindow: %s mDrawState=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "1115417974": {
       "message": "FORCED DISPLAY SIZE: %dx%d",
       "level": "INFO",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1140424002": {
+      "message": "Finished screen turning on...",
+      "level": "INFO",
+      "group": "WM_DEBUG_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/DisplayPolicy.java"
+    },
+    "1160771501": {
+      "message": "Resize reasons for w=%s:  %s surfaceResized=%b configChanged=%b dragResizingChanged=%b reportOrientationChanged=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_RESIZE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "1166381079": {
+      "message": "Execute app transition: %s, displayId: %d Callers=%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "1208313423": {
       "message": "addWindowToken: Attempted to add token: %s for non-exiting displayId=%d",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1219600119": {
+      "message": "addWindow: win=%s Callers=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/WindowToken.java"
+    },
+    "1220075598": {
+      "message": "SURFACE SIZE %dx%d: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
+    },
+    "1224184681": {
+      "message": "No longer Stopped: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1224307091": {
+      "message": "checkBootAnimationComplete: Animation complete!",
+      "level": "INFO",
+      "group": "WM_DEBUG_BOOT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "1254403969": {
+      "message": "Surface returned was null: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1288731814": {
+      "message": "WindowState.hideLw: setting mFocusMayChange true",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "1325649102": {
       "message": "Bad requesting window %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1329340614": {
+      "message": "Orientation not waiting for draw in %s, surfaceController %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
     "1331177619": {
       "message": "Attempted to add a toast window with unknown token %s.  Aborting.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1358462645": {
+      "message": "Looking for focus: %s, flags=%d, canReceive=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "1358786604": {
+      "message": "No thumbnail header bitmap for: %d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1364498663": {
+      "message": "notifyAppResumed: wasStopped=%b %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1401700824": {
+      "message": "Window drawn win=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "1417601133": {
+      "message": "Enqueueing ADD_STARTING",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1422781269": {
+      "message": "Resuming rotation after re-position",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/TaskPositioner.java"
+    },
     "1423418408": {
       "message": "unable to restore pointer icon",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1423592961": {
+      "message": "<<< CLOSE TRANSACTION removeReplacedWindows",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
+    "1430336882": {
+      "message": "findFocusedWindow: focusedApp windows not focusable using new focus @ %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "1434383382": {
       "message": "Attempted to get flag of a display that does not exist: %d",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1448683958": {
+      "message": "Override pending remote transitionSet=%b adapter=%s",
+      "level": "INFO",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "1457990604": {
+      "message": "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM_IN_PLACE transit=%s Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "1469292670": {
+      "message": "Changing focus from %s to %s displayId=%d Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "1495525537": {
+      "message": "createWallpaperAnimations()",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
+    "1497304204": {
+      "message": "Deferring rotation, rotation is paused.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "1504168072": {
+      "message": "removeIfPossible: %s callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "1515161239": {
+      "message": "removeDeadWindows: %s",
+      "level": "WARN",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1518495446": {
       "message": "removeWindowToken: Attempted to remove non-existing token: %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1519757176": {
+      "message": "setHomeApp(%s)",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
     "1521476038": {
       "message": "Attempted to set flag to a display that does not exist: %d",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1525976603": {
+      "message": "cancelAnimation(): reason=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
+    "1531527061": {
+      "message": "createAnimationAdapter(): token=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
     "1563755163": {
       "message": "Permission Denial: %s from pid=%d, uid=%d requires %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1573332272": {
+      "message": "Display id=%d selected orientation %d, got rotation %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "1577579529": {
+      "message": "win=%s destroySurfaces: appStopped=%b win.mWindowRemovalAllowed=%b win.mRemoveOnExit=%b",
+      "level": "ERROR",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "1589610525": {
+      "message": "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: anim=%s transit=%s isEntrance=true Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "1628345525": {
+      "message": "Now opening app %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "1634557978": {
+      "message": "**** Dismissing screen rotation animation",
+      "level": "INFO",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "1635462459": {
+      "message": "onMovedByResize: Moving %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RESIZE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "1653210583": {
+      "message": "Removing app %s delayed=%b animation=%s animating=%b",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1671994402": {
+      "message": "Nulling last startingData",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1677260366": {
+      "message": "Finish starting %s: first real window is shown, no animation",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1720229827": {
+      "message": "Creating animation bounds layer",
+      "level": "INFO",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "1720696061": {
       "message": "Adding window to Display that has been removed.",
       "level": "WARN",
@@ -385,31 +1879,280 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1742235936": {
+      "message": "Removing startingView=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1747941491": {
+      "message": "SURFACE controller=%s alpha=%f matrix=[%f*%f,%f*%f][%f*%f,%f*%f]: %s",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "1756082882": {
+      "message": "Orientation change skips hidden %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
+    },
+    "1762317752": {
+      "message": "Expected target stack=%s to be top most but found stack=%s",
+      "level": "WARN",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimation.java"
+    },
     "1774661765": {
       "message": "Devices still not ready after waiting %d milliseconds before attempting to detect safe mode.",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1804869745": {
+      "message": "Loading animation for app transition. transit=%s enter=%b frame=%s insets=%s surfaceInsets=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1836214582": {
+      "message": "startingData was nulled out before handling mAddStartingWindow: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1836306327": {
+      "message": "Skipping set freeze of %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1853793312": {
+      "message": "Notify removed startingWindow %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1865125884": {
+      "message": "finishScreenTurningOn: mAwake=%b, mScreenOnEarly=%b, mScreenOnFully=%b, mKeyguardDrawComplete=%b, mWindowManagerDrawComplete=%b",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/DisplayPolicy.java"
+    },
+    "1865246212": {
+      "message": "\tapp=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
     "1866772666": {
       "message": "SAFE MODE not enabled",
       "level": "INFO",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1891501279": {
+      "message": "cancelAnimation(): reason=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "1903353011": {
+      "message": "notifyAppStopped: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1921821199": {
+      "message": "Preserving %s until the new one is added",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "1947239194": {
+      "message": "Deferring rotation, still finishing previous rotation",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "1964565370": {
+      "message": "Starting remote animation",
+      "level": "INFO",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
+    },
+    "1966564525": {
+      "message": "Set freezing of %s: hidden=%b freezing=%b hiddenRequested=%b. %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "1984470582": {
+      "message": "Creating TaskScreenshotAnimatable: task: %s width: %d height: %d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/TaskScreenshotAnimatable.java"
+    },
+    "1984782949": {
+      "message": ">>> OPEN TRANSACTION animate",
+      "level": "INFO",
+      "group": "WM_SHOW_TRANSACTIONS",
+      "at": "com\/android\/server\/wm\/WindowAnimator.java"
+    },
+    "1995048598": {
+      "message": "reparent: moving app token=%s to task=%d at %d",
+      "level": "INFO",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "2016061474": {
+      "message": "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "2018454757": {
+      "message": "WS.removeImmediately: %s Already removed...",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "2018852077": {
+      "message": "Creating SplashScreenStartingData",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STARTING_WINDOW",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
+    "2028163120": {
+      "message": "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s isEntrance=%s Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
+    },
+    "2034780299": {
+      "message": "CHECK_IF_BOOT_ANIMATION_FINISHED:",
+      "level": "INFO",
+      "group": "WM_DEBUG_BOOT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "2045641491": {
+      "message": "Checking %d opening apps (frozen=%b timeout=%b)...",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
+    "2057434754": {
+      "message": "\tvisible=%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_REMOTE_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
+    },
+    "2083556954": {
+      "message": "Set mOrientationChanging of %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "2086878461": {
       "message": "Could not send command %s with parameters %s. %s",
       "level": "WARN",
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "2088592090": {
+      "message": "handleNotObscuredLocked: %s was holding screen wakelock but no longer has FLAG_KEEP_SCREEN_ON!!! called by%s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_KEEP_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+    },
+    "2096635066": {
+      "message": "Acquiring screen wakelock due to %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_KEEP_SCREEN_ON",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
+    "2114149926": {
+      "message": "Not removing %s because app died while it's visible",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ADD_REMOVE",
+      "at": "com\/android\/server\/wm\/WindowState.java"
+    },
+    "2128604122": {
+      "message": "findFocusedWindow: No focusable windows.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
+    "2128917433": {
+      "message": "onProposedRotationChanged, rotation=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
+    "2137411379": {
+      "message": "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b Callers=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+      "at": "com\/android\/server\/wm\/AppTransition.java"
     }
   },
   "groups": {
     "TEST_GROUP": {
       "tag": "WindowManagetProtoLogTest"
     },
+    "WM_DEBUG_ADD_REMOVE": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_APP_TRANSITIONS": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_APP_TRANSITIONS_ANIM": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_BOOT": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_DRAW": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_FOCUS": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_FOCUS_LIGHT": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_KEEP_SCREEN_ON": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_ORIENTATION": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_RECENTS_ANIMATIONS": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_REMOTE_ANIMATIONS": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_RESIZE": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_SCREEN_ON": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_STARTING_WINDOW": {
+      "tag": "WindowManager"
+    },
+    "WM_DEBUG_WINDOW_MOVEMENT": {
+      "tag": "WindowManager"
+    },
     "WM_ERROR": {
       "tag": "WindowManager"
+    },
+    "WM_SHOW_SURFACE_ALLOC": {
+      "tag": "WindowManager"
+    },
+    "WM_SHOW_TRANSACTIONS": {
+      "tag": "WindowManager"
     }
   }
 }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 8a76d6b..6e3e43a 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -242,7 +242,8 @@
         nsecs_t queueDuration;
     };
 
-    RingBuffer<SwapHistory, 3> mSwapHistory;
+    // Need at least 4 because we do quad buffer. Add a 5th for good measure.
+    RingBuffer<SwapHistory, 5> mSwapHistory;
     int64_t mFrameNumber = -1;
 
     // last vsync for a dropped frame due to stuffed queue
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 91f3a20..79bec92 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -42,20 +42,23 @@
  */
 interface ILocationManager
 {
-    Location getLastLocation(in LocationRequest request, String packageName);
+    Location getLastLocation(in LocationRequest request, String packageName, String featureId);
     boolean getCurrentLocation(in LocationRequest request,
             in ICancellationSignal cancellationSignal, in ILocationListener listener,
-            String packageName, String listenerIdentifier);
+            String packageName, String featureId, String listenerIdentifier);
 
     void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
-            in PendingIntent intent, String packageName, String listenerIdentifier);
+            in PendingIntent intent, String packageName, String featureId,
+            String listenerIdentifier);
     void removeUpdates(in ILocationListener listener, in PendingIntent intent, String packageName);
 
     void requestGeofence(in LocationRequest request, in Geofence geofence,
-            in PendingIntent intent, String packageName, String listenerIdentifier);
+            in PendingIntent intent, String packageName, String featureId,
+            String listenerIdentifier);
     void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName);
 
-    boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName);
+    boolean registerGnssStatusCallback(IGnssStatusListener callback, String packageName,
+            String featureId);
     void unregisterGnssStatusCallback(IGnssStatusListener callback);
 
     boolean geocoderIsPresent();
@@ -69,14 +72,14 @@
     boolean sendNiResponse(int notifId, int userResponse);
 
     boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener,
-             String packageName, String listenerIdentifier);
+             String packageName, String featureId, String listenerIdentifier);
     void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
             in String packageName);
     long getGnssCapabilities(in String packageName);
     void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
 
     boolean addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener,
-             String packageName, String listenerIdentifier);
+             String packageName, String featureId, String listenerIdentifier);
     void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
 
     int getGnssYearOfHardware();
@@ -84,7 +87,7 @@
 
     int getGnssBatchSize(String packageName);
     boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName,
-             String listenerIdentifier);
+             String featureId, String listenerIdentifier);
     void removeGnssBatchingCallback();
     boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName);
     void flushGnssBatch(String packageName);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 9e17e95..010f92f 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -515,7 +515,8 @@
     @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
     public Location getLastLocation() {
         try {
-            return mService.getLastLocation(null, mContext.getPackageName());
+            return mService.getLastLocation(null, mContext.getPackageName(),
+                    mContext.getFeatureId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -547,7 +548,8 @@
                 provider, 0, 0, true);
 
         try {
-            return mService.getLastLocation(request, mContext.getPackageName());
+            return mService.getLastLocation(request, mContext.getPackageName(),
+                    mContext.getFeatureId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -641,7 +643,7 @@
 
         try {
             if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal,
-                    listenerTransport, mContext.getPackageName(),
+                    listenerTransport, mContext.getPackageName(), mContext.getFeatureId(),
                     getListenerIdentifier(consumer))) {
                 listenerTransport.register(mContext.getSystemService(AlarmManager.class),
                         remoteCancellationSignal);
@@ -1085,7 +1087,8 @@
             boolean registered = false;
             try {
                 mService.requestLocationUpdates(locationRequest, transport, null,
-                        mContext.getPackageName(), getListenerIdentifier(listener));
+                        mContext.getPackageName(), mContext.getFeatureId(),
+                        getListenerIdentifier(listener));
                 registered = true;
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -1130,7 +1133,8 @@
 
         try {
             mService.requestLocationUpdates(locationRequest, null, pendingIntent,
-                    mContext.getPackageName(), getListenerIdentifier(pendingIntent));
+                    mContext.getPackageName(), mContext.getFeatureId(),
+                    getListenerIdentifier(pendingIntent));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1594,7 +1598,7 @@
         LocationRequest request = new LocationRequest().setExpireIn(expiration);
         try {
             mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
-                    getListenerIdentifier(intent));
+                    mContext.getFeatureId(), getListenerIdentifier(intent));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1672,7 +1676,7 @@
 
         try {
             mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
-                    getListenerIdentifier(intent));
+                    mContext.getFeatureId(), getListenerIdentifier(intent));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2748,7 +2752,7 @@
 
             mListenerTransport = new GnssStatusListener();
             return mService.registerGnssStatusCallback(mListenerTransport,
-                    mContext.getPackageName());
+                    mContext.getPackageName(), mContext.getFeatureId());
         }
 
         @Override
@@ -2808,7 +2812,8 @@
 
             mListenerTransport = new GnssMeasurementsListener();
             return mService.addGnssMeasurementsListener(mListenerTransport,
-                    mContext.getPackageName(), "gnss measurement callback");
+                    mContext.getPackageName(), mContext.getFeatureId(),
+                    "gnss measurement callback");
         }
 
         @Override
@@ -2844,7 +2849,8 @@
 
             mListenerTransport = new GnssNavigationMessageListener();
             return mService.addGnssNavigationMessageListener(mListenerTransport,
-                    mContext.getPackageName(), "gnss navigation callback");
+                    mContext.getPackageName(), mContext.getFeatureId(),
+                    "gnss navigation callback");
         }
 
         @Override
@@ -2880,7 +2886,7 @@
 
             mListenerTransport = new BatchedLocationCallback();
             return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName(),
-                     "batched location callback");
+                     mContext.getFeatureId(), "batched location callback");
         }
 
         @Override
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 53bc65d..bb731a8 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
@@ -26,6 +27,8 @@
 import android.media.audiopolicy.AudioMix;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Map;
 
@@ -431,6 +434,50 @@
     public static final int DEAD_OBJECT        = -6;
     public static final int WOULD_BLOCK        = -7;
 
+    /** @hide */
+    @IntDef({
+            SUCCESS,
+            ERROR,
+            BAD_VALUE,
+            INVALID_OPERATION,
+            PERMISSION_DENIED,
+            NO_INIT,
+            DEAD_OBJECT,
+            WOULD_BLOCK
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AudioSystemError {}
+
+    /**
+     * Convert an int error value to its String value for readability.
+     * Accepted error values are the java AudioSystem errors, matching android_media_AudioErrors.h,
+     * which map onto the native status_t type.
+     * @param error one of the java AudioSystem errors
+     * @return a human-readable string
+     */
+    public static String audioSystemErrorToString(@AudioSystemError int error) {
+        switch(error) {
+            case SUCCESS:
+                return "SUCCESS";
+            case ERROR:
+                return "ERROR";
+            case BAD_VALUE:
+                return "BAD_VALUE";
+            case INVALID_OPERATION:
+                return "INVALID_OPERATION";
+            case PERMISSION_DENIED:
+                return "PERMISSION_DENIED";
+            case NO_INIT:
+                return "NO_INIT";
+            case DEAD_OBJECT:
+                return "DEAD_OBJECT";
+            case WOULD_BLOCK:
+                return "WOULD_BLOCK";
+            default:
+                return ("unknown error:" + error);
+        }
+    }
+
     /*
      * AudioPolicyService methods
      */
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 39474e1..01f1250 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -853,6 +853,10 @@
                 Log.v(TAG, "notifyVolumeAdjust: " + adjustment);
             }
         }
+
+        public void notifyUnregistration() {
+            setRegistration(null);
+        }
     };
 
     //==================================================
diff --git a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
index 107e7cd..1d58151 100644
--- a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
+++ b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
@@ -34,4 +34,8 @@
 
     // callback for volume events
     void notifyVolumeAdjust(int adjustment);
+
+    // callback for unregistration (e.g. if policy couldn't automatically be re-registered after
+    // an audioserver crash)
+    void notifyUnregistration();
 }
diff --git a/media/java/android/media/midi/MidiDeviceInfo.aidl b/media/java/android/media/midi/MidiDeviceInfo.aidl
index 5b2ac9b..a248204 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.aidl
+++ b/media/java/android/media/midi/MidiDeviceInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.media.midi;
 
-parcelable MidiDeviceInfo cpp_header "media/MidiDeviceInfo.h";
+parcelable MidiDeviceInfo cpp_header "MidiDeviceInfo.h";
diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp
index 35b7b01..e1945dd 100644
--- a/media/jni/soundpool/Android.bp
+++ b/media/jni/soundpool/Android.bp
@@ -3,11 +3,16 @@
 
     srcs: [
         "android_media_SoundPool.cpp",
+        "Sound.cpp",
+        "SoundDecoder.cpp",
+        "SoundManager.cpp",
         "SoundPool.cpp",
-        "SoundPoolThread.cpp",
+        "Stream.cpp",
+        "StreamManager.cpp",
     ],
 
     shared_libs: [
+        "libaudioutils",
         "liblog",
         "libcutils",
         "libutils",
diff --git a/media/jni/soundpool/Sound.cpp b/media/jni/soundpool/Sound.cpp
new file mode 100644
index 0000000..0bbc3e4
--- /dev/null
+++ b/media/jni/soundpool/Sound.cpp
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::Sound"
+#include <utils/Log.h>
+
+#include "Sound.h"
+
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/NdkMediaFormat.h>
+
+namespace android::soundpool {
+
+constexpr uint32_t kMaxSampleRate = 192000;
+constexpr size_t   kDefaultHeapSize = 1024 * 1024; // 1MB (compatible with low mem devices)
+
+Sound::Sound(int32_t soundID, int fd, int64_t offset, int64_t length)
+    : mSoundID(soundID)
+    , mFd(dup(fd))
+    , mOffset(offset)
+    , mLength(length)
+{
+    ALOGV("%s(soundID=%d, fd=%d, offset=%lld, length=%lld)",
+            __func__, soundID, fd, (long long)offset, (long long)length);
+    ALOGW_IF(mFd == -1, "Unable to dup descriptor %d", fd);
+}
+
+Sound::~Sound()
+{
+    ALOGV("%s(soundID=%d, fd=%d)", __func__, mSoundID, mFd.get());
+}
+
+static status_t decode(int fd, int64_t offset, int64_t length,
+        uint32_t *rate, int32_t *channelCount, audio_format_t *audioFormat,
+        audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
+        size_t *sizeInBytes) {
+    ALOGV("%s(fd=%d, offset=%lld, length=%lld, ...)",
+            __func__, fd, (long long)offset, (long long)length);
+    std::unique_ptr<AMediaExtractor, decltype(&AMediaExtractor_delete)> ex{
+            AMediaExtractor_new(), &AMediaExtractor_delete};
+    status_t err = AMediaExtractor_setDataSourceFd(ex.get(), fd, offset, length);
+
+    if (err != AMEDIA_OK) {
+        return err;
+    }
+
+    *audioFormat = AUDIO_FORMAT_PCM_16_BIT;  // default format for audio codecs.
+    const size_t numTracks = AMediaExtractor_getTrackCount(ex.get());
+    for (size_t i = 0; i < numTracks; i++) {
+        std::unique_ptr<AMediaFormat, decltype(&AMediaFormat_delete)> format{
+                AMediaExtractor_getTrackFormat(ex.get(), i), &AMediaFormat_delete};
+        const char *mime;
+        if (!AMediaFormat_getString(format.get(),  AMEDIAFORMAT_KEY_MIME, &mime)) {
+            return UNKNOWN_ERROR;
+        }
+        if (strncmp(mime, "audio/", 6) == 0) {
+            std::unique_ptr<AMediaCodec, decltype(&AMediaCodec_delete)> codec{
+                    AMediaCodec_createDecoderByType(mime), &AMediaCodec_delete};
+            if (codec == nullptr
+                    || AMediaCodec_configure(codec.get(), format.get(),
+                            nullptr /* window */, nullptr /* drm */, 0 /* flags */) != AMEDIA_OK
+                    || AMediaCodec_start(codec.get()) != AMEDIA_OK
+                    || AMediaExtractor_selectTrack(ex.get(), i) != AMEDIA_OK) {
+                return UNKNOWN_ERROR;
+            }
+
+            bool sawInputEOS = false;
+            bool sawOutputEOS = false;
+            uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
+            size_t available = heap->getSize();
+            size_t written = 0;
+            format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format.
+
+            while (!sawOutputEOS) {
+                if (!sawInputEOS) {
+                    ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec.get(), 5000);
+                    ALOGV("%s: input buffer %zd", __func__, bufidx);
+                    if (bufidx >= 0) {
+                        size_t bufsize;
+                        uint8_t * const buf = AMediaCodec_getInputBuffer(
+                                codec.get(), bufidx, &bufsize);
+                        if (buf == nullptr) {
+                            ALOGE("%s: AMediaCodec_getInputBuffer returned nullptr, short decode",
+                                    __func__);
+                            break;
+                        }
+                        int sampleSize = AMediaExtractor_readSampleData(ex.get(), buf, bufsize);
+                        ALOGV("%s: read %d", __func__, sampleSize);
+                        if (sampleSize < 0) {
+                            sampleSize = 0;
+                            sawInputEOS = true;
+                            ALOGV("%s: EOS", __func__);
+                        }
+                        const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex.get());
+
+                        const media_status_t mstatus = AMediaCodec_queueInputBuffer(
+                                codec.get(), bufidx,
+                                0 /* offset */, sampleSize, presentationTimeUs,
+                                sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+                        if (mstatus != AMEDIA_OK) {
+                            // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+                            ALOGE("%s: AMediaCodec_queueInputBuffer returned status %d,"
+                                    "short decode",
+                                    __func__, (int)mstatus);
+                            break;
+                        }
+                        (void)AMediaExtractor_advance(ex.get());
+                    }
+                }
+
+                AMediaCodecBufferInfo info;
+                const int status = AMediaCodec_dequeueOutputBuffer(codec.get(), &info, 1);
+                ALOGV("%s: dequeueoutput returned: %d", __func__, status);
+                if (status >= 0) {
+                    if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+                        ALOGV("%s: output EOS", __func__);
+                        sawOutputEOS = true;
+                    }
+                    ALOGV("%s: got decoded buffer size %d", __func__, info.size);
+
+                    const uint8_t * const buf = AMediaCodec_getOutputBuffer(
+                            codec.get(), status, nullptr /* out_size */);
+                    if (buf == nullptr) {
+                        ALOGE("%s: AMediaCodec_getOutputBuffer returned nullptr, short decode",
+                                __func__);
+                        break;
+                    }
+                    const size_t dataSize = std::min((size_t)info.size, available);
+                    memcpy(writePos, buf + info.offset, dataSize);
+                    writePos += dataSize;
+                    written += dataSize;
+                    available -= dataSize;
+                    const media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
+                            codec.get(), status, false /* render */);
+                    if (mstatus != AMEDIA_OK) {
+                        // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
+                        ALOGE("%s: AMediaCodec_releaseOutputBuffer"
+                                " returned status %d, short decode",
+                                __func__, (int)mstatus);
+                        break;
+                    }
+                    if (available == 0) {
+                        // there might be more data, but there's no space for it
+                        sawOutputEOS = true;
+                    }
+                } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+                    ALOGV("%s: output buffers changed", __func__);
+                } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+                    format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format
+                    ALOGV("%s: format changed to: %s",
+                           __func__, AMediaFormat_toString(format.get()));
+                } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+                    ALOGV("%s: no output buffer right now", __func__);
+                } else if (status <= AMEDIA_ERROR_BASE) {
+                    ALOGE("%s: decode error: %d", __func__, status);
+                    break;
+                } else {
+                    ALOGV("%s: unexpected info code: %d", __func__, status);
+                }
+            }
+
+            (void)AMediaCodec_stop(codec.get());
+            if (!AMediaFormat_getInt32(
+                    format.get(), AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
+                !AMediaFormat_getInt32(
+                    format.get(), AMEDIAFORMAT_KEY_CHANNEL_COUNT, channelCount)) {
+                return UNKNOWN_ERROR;
+            }
+            if (!AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_CHANNEL_MASK,
+                    (int32_t*) channelMask)) {
+                *channelMask = AUDIO_CHANNEL_NONE;
+            }
+            *sizeInBytes = written;
+            return OK;
+        }
+    }
+    return UNKNOWN_ERROR;
+}
+
+status_t Sound::doLoad()
+{
+    ALOGV("%s()", __func__);
+    status_t status = NO_INIT;
+    if (mFd.get() != -1) {
+        mHeap = new MemoryHeapBase(kDefaultHeapSize);
+
+        ALOGV("%s: start decode", __func__);
+        uint32_t sampleRate;
+        int32_t channelCount;
+        audio_format_t format;
+        audio_channel_mask_t channelMask;
+        status_t status = decode(mFd.get(), mOffset, mLength, &sampleRate, &channelCount, &format,
+                        &channelMask, mHeap, &mSizeInBytes);
+        ALOGV("%s: close(%d)", __func__, mFd.get());
+        mFd.reset();  // close
+
+        if (status != NO_ERROR) {
+            ALOGE("%s: unable to load sound", __func__);
+        } else if (sampleRate > kMaxSampleRate) {
+            ALOGE("%s: sample rate (%u) out of range", __func__, sampleRate);
+            status = BAD_VALUE;
+        } else if (channelCount < 1 || channelCount > FCC_8) {
+            ALOGE("%s: sample channel count (%d) out of range", __func__, channelCount);
+            status = BAD_VALUE;
+        } else {
+            // Correctly loaded, proper parameters
+            ALOGV("%s: pointer = %p, sizeInBytes = %zu, sampleRate = %u, channelCount = %d",
+                  __func__, mHeap->getBase(), mSizeInBytes, sampleRate, channelCount);
+            mData = new MemoryBase(mHeap, 0, mSizeInBytes);
+            mSampleRate = sampleRate;
+            mChannelCount = channelCount;
+            mFormat = format;
+            mChannelMask = channelMask;
+            mState = READY;  // this should be last, as it is an atomic sync point
+            return NO_ERROR;
+        }
+    } else {
+        ALOGE("%s: uninitialized fd, dup failed", __func__);
+    }
+    // ERROR handling
+    mHeap.clear();
+    mState = DECODE_ERROR; // this should be last, as it is an atomic sync point
+    return status;
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/Sound.h b/media/jni/soundpool/Sound.h
new file mode 100644
index 0000000..efe940a
--- /dev/null
+++ b/media/jni/soundpool/Sound.h
@@ -0,0 +1,93 @@
+/*
+ * 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 <android-base/unique_fd.h>
+#include <binder/MemoryBase.h>
+#include <binder/MemoryHeapBase.h>
+#include <system/audio.h>
+
+namespace android::soundpool {
+
+class SoundDecoder;
+
+/**
+ * Sound is a resource used by SoundPool, referenced by soundID.
+ *
+ * After loading, it is effectively const so no locking required.
+ * However, in order to guarantee that all the values have been
+ * written properly and read properly, we use the mState as an atomic synchronization
+ * point.  So if getState() shows READY, then all the other getters may
+ * be safely read.
+ *
+ * Technical details:
+ * We access the mState atomic value through memory_order_seq_cst
+ *
+ * https://en.cppreference.com/w/cpp/atomic/memory_order
+ *
+ * which provides memory barriers.  So if the last value written by the SoundDecoder
+ * is mState, then the compiler ensures no other prior writes by SoundDecoder will be
+ * reordered afterwards, and memory barrier is placed (as necessary) to ensure the
+ * cache is visible to other processors.
+ *
+ * Likewise, if the first value read by SoundPool is mState,
+ * the compiler ensures no reads for that thread will be reordered before mState is read,
+ * and a memory barrier is placed (as necessary) to ensure that the cache is properly
+ * updated with other processor's writes before reading.
+ *
+ * See https://developer.android.com/training/articles/smp for discussions about
+ * the variant load-acquire, store-release semantics.
+ */
+class Sound {
+    friend SoundDecoder;  // calls doLoad().
+
+public:
+    enum sound_state : int32_t { LOADING, READY, DECODE_ERROR };
+    // A sound starts in the LOADING state and transitions only once
+    // to either READY or DECODE_ERROR when doLoad() is called.
+
+    Sound(int soundID, int fd, int64_t offset, int64_t length);
+    ~Sound();
+
+    int32_t getSoundID() const { return mSoundID; }
+    int32_t getChannelCount() const { return mChannelCount; }
+    uint32_t getSampleRate() const { return mSampleRate; }
+    audio_format_t getFormat() const { return mFormat; }
+    audio_channel_mask_t getChannelMask() const { return mChannelMask; }
+    size_t getSizeInBytes() const { return mSizeInBytes; }
+    sound_state getState() const { return mState; }
+    uint8_t* getData() const { return static_cast<uint8_t*>(mData->unsecurePointer()); }
+    sp<IMemory> getIMemory() const { return mData; }
+
+private:
+    status_t doLoad();  // only SoundDecoder accesses this.
+
+    size_t               mSizeInBytes = 0;
+    const int32_t        mSoundID;
+    uint32_t             mSampleRate = 0;
+    std::atomic<sound_state> mState = LOADING; // used as synchronization point
+    int32_t              mChannelCount = 0;
+    audio_format_t       mFormat = AUDIO_FORMAT_INVALID;
+    audio_channel_mask_t mChannelMask = AUDIO_CHANNEL_NONE;
+    base::unique_fd      mFd;     // initialized in constructor, reset to -1 after loading
+    const int64_t        mOffset; // int64_t to match java long, see off64_t
+    const int64_t        mLength; // int64_t to match java long, see off64_t
+    sp<IMemory>          mData;
+    sp<MemoryHeapBase>   mHeap;
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundDecoder.cpp b/media/jni/soundpool/SoundDecoder.cpp
new file mode 100644
index 0000000..12200ef
--- /dev/null
+++ b/media/jni/soundpool/SoundDecoder.cpp
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::SoundDecoder"
+#include "utils/Log.h"
+
+#include "SoundDecoder.h"
+
+namespace android::soundpool {
+
+// Maximum Samples that can be background decoded before we block the caller.
+static constexpr size_t kMaxQueueSize = 128;
+
+// The amount of time we wait for a new Sound decode request
+// before the SoundDecoder thread closes.
+static constexpr int32_t kWaitTimeBeforeCloseMs = 1000;
+
+SoundDecoder::SoundDecoder(SoundManager* soundManager, size_t threads)
+    : mSoundManager(soundManager)
+{
+    ALOGV("%s(%p, %zu)", __func__, soundManager, threads);
+    // ThreadPool is created, but we don't launch any threads.
+    mThreadPool = std::make_unique<ThreadPool>(
+            std::min(threads, (size_t)std::thread::hardware_concurrency()),
+            "SoundDecoder_");
+}
+
+SoundDecoder::~SoundDecoder()
+{
+    ALOGV("%s()", __func__);
+    quit();
+}
+
+void SoundDecoder::quit()
+{
+    ALOGV("%s()", __func__);
+    {
+        std::lock_guard lock(mLock);
+        mQuit = true;
+        mQueueSpaceAvailable.notify_all(); // notify all load waiters
+        mQueueDataAvailable.notify_all();  // notify all worker threads
+    }
+    mThreadPool->quit();
+}
+
+void SoundDecoder::run(int32_t id __unused /* ALOGV only */)
+{
+    ALOGV("%s(%d): entering", __func__, id);
+    std::unique_lock lock(mLock);
+    while (!mQuit) {
+        if (mSoundIDs.size() == 0) {
+            ALOGV("%s(%d): waiting", __func__, id);
+            mQueueDataAvailable.wait_for(
+                    lock, std::chrono::duration<int32_t, std::milli>(kWaitTimeBeforeCloseMs));
+            if (mSoundIDs.size() == 0) {
+                break; // no new sound, exit this thread.
+            }
+            continue;
+        }
+        const int32_t soundID = mSoundIDs.front();
+        mSoundIDs.pop_front();
+        mQueueSpaceAvailable.notify_one();
+        ALOGV("%s(%d): processing soundID: %d  size: %zu", __func__, id, soundID, mSoundIDs.size());
+        lock.unlock();
+        std::shared_ptr<Sound> sound = mSoundManager->findSound(soundID);
+        status_t status = NO_INIT;
+        if (sound.get() != nullptr) {
+            status = sound->doLoad();
+        }
+        ALOGV("%s(%d): notifying loaded soundID:%d  status:%d", __func__, id, soundID, status);
+        mSoundManager->notify(SoundPoolEvent(SoundPoolEvent::SOUND_LOADED, soundID, status));
+        lock.lock();
+    }
+    ALOGV("%s(%d): exiting", __func__, id);
+}
+
+void SoundDecoder::loadSound(int32_t soundID)
+{
+    ALOGV("%s(%d)", __func__, soundID);
+    size_t pendingSounds;
+    {
+        std::unique_lock lock(mLock);
+        while (mSoundIDs.size() == kMaxQueueSize) {
+            if (mQuit) return;
+            ALOGV("%s: waiting soundID: %d size: %zu", __func__, soundID, mSoundIDs.size());
+            mQueueSpaceAvailable.wait(lock);
+        }
+        if (mQuit) return;
+        mSoundIDs.push_back(soundID);
+        mQueueDataAvailable.notify_one();
+        ALOGV("%s: adding soundID: %d  size: %zu", __func__, soundID, mSoundIDs.size());
+        pendingSounds = mSoundIDs.size();
+    }
+    // Launch threads as needed.  The "as needed" is weakly consistent as we release mLock.
+    if (pendingSounds > mThreadPool->getActiveThreadCount()) {
+        const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+        ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+    }
+}
+
+} // end namespace android::soundpool
diff --git a/media/jni/soundpool/SoundDecoder.h b/media/jni/soundpool/SoundDecoder.h
new file mode 100644
index 0000000..1288943
--- /dev/null
+++ b/media/jni/soundpool/SoundDecoder.h
@@ -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.
+ */
+
+#pragma once
+
+#include "SoundPool.h"
+
+#include <deque>
+#include <mutex>
+
+namespace android::soundpool {
+
+/**
+ * SoundDecoder handles background decoding tasks.
+ */
+class SoundDecoder {
+public:
+    SoundDecoder(SoundManager* soundManager, size_t threads);
+    ~SoundDecoder();
+    void loadSound(int32_t soundID);
+    void quit();
+
+private:
+    void run(int32_t id);                       // The decode thread function.
+
+    SoundManager* const     mSoundManager;      // set in constructor, has own lock
+    std::unique_ptr<ThreadPool> mThreadPool;    // set in constructor, has own lock
+
+    std::mutex              mLock;
+    std::condition_variable mQueueSpaceAvailable;
+    std::condition_variable mQueueDataAvailable;
+
+    std::deque<int32_t>     mSoundIDs;            // GUARDED_BY(mLock);
+    bool                    mQuit = false;        // GUARDED_BY(mLock);
+};
+
+} // end namespace android::soundpool
+
diff --git a/media/jni/soundpool/SoundManager.cpp b/media/jni/soundpool/SoundManager.cpp
new file mode 100644
index 0000000..3c625bf
--- /dev/null
+++ b/media/jni/soundpool/SoundManager.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::SoundManager"
+#include <utils/Log.h>
+
+#include "SoundManager.h"
+
+#include <thread>
+
+#include "SoundDecoder.h"
+
+namespace android::soundpool {
+
+static const size_t kDecoderThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
+
+SoundManager::SoundManager()
+    : mDecoder{std::make_unique<SoundDecoder>(this, kDecoderThreads)}
+{
+    ALOGV("%s()", __func__);
+}
+
+SoundManager::~SoundManager()
+{
+    ALOGV("%s()", __func__);
+    mDecoder->quit();
+
+    std::lock_guard lock(mSoundManagerLock);
+    mSounds.clear();
+}
+
+int32_t SoundManager::load(int fd, int64_t offset, int64_t length, int32_t priority __unused)
+{
+    ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
+            __func__, fd, (long long)offset, (long long)length, priority);
+    int32_t soundID;
+    {
+        std::lock_guard lock(mSoundManagerLock);
+        // mNextSoundID is always positive and does not "integer overflow"
+        do {
+            mNextSoundID = mNextSoundID == INT32_MAX ? 1 : mNextSoundID + 1;
+        } while (findSound_l(mNextSoundID) != nullptr);
+        soundID = mNextSoundID;
+        auto sound = std::make_shared<Sound>(soundID, fd, offset, length);
+        mSounds.emplace(soundID, sound);
+    }
+    // mDecoder->loadSound() must be called outside of mSoundManagerLock.
+    // mDecoder->loadSound() may block on mDecoder message queue space;
+    // the message queue emptying may block on SoundManager::findSound().
+    //
+    // It is theoretically possible that sound loads might decode out-of-order.
+    mDecoder->loadSound(soundID);
+    return soundID;
+}
+
+bool SoundManager::unload(int32_t soundID)
+{
+    ALOGV("%s(soundID=%d)", __func__, soundID);
+    std::lock_guard lock(mSoundManagerLock);
+    return mSounds.erase(soundID) > 0; // erase() returns number of sounds removed.
+}
+
+std::shared_ptr<Sound> SoundManager::findSound(int32_t soundID) const
+{
+    std::lock_guard lock(mSoundManagerLock);
+    return findSound_l(soundID);
+}
+
+std::shared_ptr<Sound> SoundManager::findSound_l(int32_t soundID) const
+{
+    auto it = mSounds.find(soundID);
+    return it != mSounds.end() ? it->second : nullptr;
+}
+
+void SoundManager::setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* user)
+{
+    mCallbackHandler.setCallback(soundPool, callback, user);
+}
+
+void SoundManager::notify(SoundPoolEvent event)
+{
+    mCallbackHandler.notify(event);
+}
+
+void* SoundManager::getUserData() const
+{
+    return mCallbackHandler.getUserData();
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundManager.h b/media/jni/soundpool/SoundManager.h
new file mode 100644
index 0000000..9201e78
--- /dev/null
+++ b/media/jni/soundpool/SoundManager.h
@@ -0,0 +1,110 @@
+/*
+ * 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 "Sound.h"
+
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+
+class SoundPool;
+
+// for queued events
+class SoundPoolEvent {
+public:
+    explicit SoundPoolEvent(int msg, int arg1 = 0, int arg2 = 0) :
+        mMsg(msg), mArg1(arg1), mArg2(arg2) {}
+    const int mMsg;   // MessageType
+    const int mArg1;  // soundID
+    const int mArg2;  // status
+    enum MessageType { INVALID, SOUND_LOADED };
+};
+
+// callback function prototype
+typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
+
+} // namespace android
+
+namespace android::soundpool {
+
+// This class manages Sounds for the SoundPool.
+class SoundManager {
+public:
+    SoundManager();
+    ~SoundManager();
+
+    // Matches corresponding SoundPool API functions
+    int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
+    bool unload(int32_t soundID);
+    void setCallback(SoundPool* soundPool, SoundPoolCallback* callback, void* user);
+    void* getUserData() const;
+
+    // SoundPool and SoundDecoder access
+    std::shared_ptr<Sound> findSound(int32_t soundID) const;
+
+    // from the SoundDecoder
+    void notify(SoundPoolEvent event);
+
+private:
+
+    // CallbackHandler is used to manage notifications back to the app when a sound
+    // has been loaded.  It uses a recursive lock to allow setting the callback
+    // during the callback.
+    class CallbackHandler {
+    public:
+        void setCallback(SoundPool *soundPool, SoundPoolCallback* callback, void* userData)
+        {
+            std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+            mSoundPool = soundPool;
+            mCallback = callback;
+            mUserData = userData;
+        }
+        void notify(SoundPoolEvent event) const
+        {
+            std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+            if (mCallback != nullptr) {
+                mCallback(event, mSoundPool, mUserData);
+                // Note: mCallback may call setCallback().
+                // so mCallback, mUserData may have changed.
+            }
+        }
+        void* getUserData() const
+        {
+            std::lock_guard<std::recursive_mutex> lock(mCallbackLock);
+            return mUserData;
+        }
+    private:
+        mutable std::recursive_mutex  mCallbackLock; // allow mCallback to setCallback().
+        SoundPool*          mSoundPool = nullptr; // GUARDED_BY(mCallbackLock)
+        SoundPoolCallback*  mCallback = nullptr;  // GUARDED_BY(mCallbackLock)
+        void*               mUserData = nullptr;  // GUARDED_BY(mCallbackLock)
+    };
+
+    std::shared_ptr<Sound> findSound_l(int32_t soundID) const;
+
+    // The following variables are initialized in constructor and can be accessed anytime.
+    CallbackHandler         mCallbackHandler;              // has its own lock
+    const std::unique_ptr<SoundDecoder> mDecoder;          // has its own lock
+
+    mutable std::mutex      mSoundManagerLock;
+    std::unordered_map<int, std::shared_ptr<Sound>> mSounds; // GUARDED_BY(mSoundManagerLock)
+    int32_t                 mNextSoundID = 0;    // GUARDED_BY(mSoundManagerLock)
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 102bbf0..ac44843 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -16,1124 +16,230 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "SoundPool"
-
-#include <chrono>
-#include <inttypes.h>
-#include <thread>
 #include <utils/Log.h>
 
-#define USE_SHARED_MEM_BUFFER
+#include <algorithm>
+#include <thread>
 
-#include <media/AudioTrack.h>
 #include "SoundPool.h"
-#include "SoundPoolThread.h"
-#include <media/NdkMediaCodec.h>
-#include <media/NdkMediaExtractor.h>
-#include <media/NdkMediaFormat.h>
 
 namespace android
 {
 
-int kDefaultBufferCount = 4;
-uint32_t kMaxSampleRate = 48000;
-uint32_t kDefaultSampleRate = 44100;
-uint32_t kDefaultFrameCount = 1200;
-size_t kDefaultHeapSize = 1024 * 1024; // 1MB
+// kManagerThreads = 1 historically.
+// Not really necessary to have more than one, but it does speed things up by about
+// 25% having 2 threads instead of 1 when playing many sounds.  Having many threads
+// could starve other AudioFlinger clients with SoundPool activity. It may also cause
+// issues with app loading, e.g. Camera.
+static const size_t kStreamManagerThreads = std::thread::hardware_concurrency() >= 4 ? 2 : 1;
 
+// kUseApiLock = true prior to R.
+// Set to true to prevent multiple users access internal to the SoundPool API.
+// Set to false to make the SoundPool methods weakly consistent.  When set to false,
+// only AutoPause and AutoResume are locked, which are the only two methods that
+// require API level locking for consistency.
+static constexpr bool kUseApiLock = false;
 
-SoundPool::SoundPool(int maxChannels, const audio_attributes_t* pAttributes)
+namespace {
+// Check input arguments to SoundPool - return "true" to reject request.
+
+bool checkVolume(float *leftVolume, float *rightVolume)
 {
-    ALOGV("SoundPool constructor: maxChannels=%d, attr.usage=%d, attr.flags=0x%x, attr.tags=%s",
-            maxChannels, pAttributes->usage, pAttributes->flags, pAttributes->tags);
-
-    // check limits
-    mMaxChannels = maxChannels;
-    if (mMaxChannels < 1) {
-        mMaxChannels = 1;
+    if (*leftVolume != std::clamp(*leftVolume, 0.f, 1.f) ||
+            *rightVolume != std::clamp(*rightVolume, 0.f, 1.f)) {
+        ALOGI("volume l=%f r=%f out of (0.f, 1.f) bounds, using 1.f", *leftVolume, *rightVolume);
+        // for backward compatibility use 1.f.
+        *leftVolume = *rightVolume = 1.f;
     }
-    else if (mMaxChannels > 32) {
-        mMaxChannels = 32;
+    return false;
+}
+
+bool checkRate(float *rate)
+{
+    if (*rate != std::clamp(*rate, 0.125f, 8.f)) {
+        ALOGI("rate %f out of (0.125f, 8.f) bounds, clamping", *rate);
+        // for backward compatibility just clamp
+        *rate = std::clamp(*rate, 0.125f, 8.f);
     }
-    ALOGW_IF(maxChannels != mMaxChannels, "App requested %d channels", maxChannels);
+    return false;
+}
 
-    mQuit = false;
-    mMuted = false;
-    mDecodeThread = 0;
-    memcpy(&mAttributes, pAttributes, sizeof(audio_attributes_t));
-    mAllocated = 0;
-    mNextSampleID = 0;
-    mNextChannelID = 0;
-
-    mCallback = 0;
-    mUserData = 0;
-
-    mChannelPool = new SoundChannel[mMaxChannels];
-    for (int i = 0; i < mMaxChannels; ++i) {
-        mChannelPool[i].init(this);
-        mChannels.push_back(&mChannelPool[i]);
+bool checkPriority(int32_t *priority)
+{
+    if (*priority < 0) {
+        ALOGI("negative priority %d, should be >= 0.", *priority);
+        // for backward compatibility, ignore.
     }
+    return false;
+}
 
-    // start decode thread
-    startThreads();
+bool checkLoop(int32_t *loop)
+{
+    if (*loop < -1) {
+        ALOGI("loop %d, should be >= -1", *loop);
+        *loop = -1;
+    }
+    return false;
+}
+
+} // namespace
+
+SoundPool::SoundPool(int32_t maxStreams, const audio_attributes_t* attributes)
+    : mStreamManager(maxStreams, kStreamManagerThreads, attributes)
+{
+    ALOGV("%s(maxStreams=%d, attr={ content_type=%d, usage=%d, flags=0x%x, tags=%s })",
+            __func__, maxStreams,
+            attributes->content_type, attributes->usage, attributes->flags, attributes->tags);
 }
 
 SoundPool::~SoundPool()
 {
-    ALOGV("SoundPool destructor");
-    mDecodeThread->quit();
-    quit();
-
-    Mutex::Autolock lock(&mLock);
-
-    mChannels.clear();
-    if (mChannelPool)
-        delete [] mChannelPool;
-    // clean up samples
-    ALOGV("clear samples");
-    mSamples.clear();
-
-    if (mDecodeThread)
-        delete mDecodeThread;
+    ALOGV("%s()", __func__);
 }
 
-void SoundPool::addToRestartList(SoundChannel* channel)
+int32_t SoundPool::load(int fd, int64_t offset, int64_t length, int32_t priority)
 {
-    Mutex::Autolock lock(&mRestartLock);
-    if (!mQuit) {
-        mRestart.push_back(channel);
-        mCondition.signal();
-    }
+    ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
+            __func__, fd, (long long)offset, (long long)length, priority);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    return mSoundManager.load(fd, offset, length, priority);
 }
 
-void SoundPool::addToStopList(SoundChannel* channel)
+bool SoundPool::unload(int32_t soundID)
 {
-    Mutex::Autolock lock(&mRestartLock);
-    if (!mQuit) {
-        mStop.push_back(channel);
-        mCondition.signal();
-    }
+    ALOGV("%s(%d)", __func__, soundID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    return mSoundManager.unload(soundID);
 }
 
-int SoundPool::beginThread(void* arg)
+int32_t SoundPool::play(int32_t soundID, float leftVolume, float rightVolume,
+        int32_t priority, int32_t loop, float rate)
 {
-    SoundPool* p = (SoundPool*)arg;
-    return p->run();
-}
+    ALOGV("%s(soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
+            __func__, soundID, leftVolume, rightVolume, priority, loop, rate);
 
-int SoundPool::run()
-{
-    mRestartLock.lock();
-    while (!mQuit) {
-        mCondition.wait(mRestartLock);
-        ALOGV("awake");
-        if (mQuit) break;
+    // New for R: check arguments to ensure track can be created.
+    // If SoundPool defers the creation of the AudioTrack to the StreamManager thread,
+    // the failure to create may not be visible to the caller, so this precheck is needed.
+    if (checkVolume(&leftVolume, &rightVolume)
+            || checkPriority(&priority)
+            || checkLoop(&loop)
+            || checkRate(&rate)) return 0;
 
-        while (!mStop.empty()) {
-            SoundChannel* channel;
-            ALOGV("Getting channel from stop list");
-            List<SoundChannel* >::iterator iter = mStop.begin();
-            channel = *iter;
-            mStop.erase(iter);
-            mRestartLock.unlock();
-            if (channel != 0) {
-                Mutex::Autolock lock(&mLock);
-                channel->stop();
-            }
-            mRestartLock.lock();
-            if (mQuit) break;
-        }
-
-        while (!mRestart.empty()) {
-            SoundChannel* channel;
-            ALOGV("Getting channel from list");
-            List<SoundChannel*>::iterator iter = mRestart.begin();
-            channel = *iter;
-            mRestart.erase(iter);
-            mRestartLock.unlock();
-            if (channel != 0) {
-                Mutex::Autolock lock(&mLock);
-                channel->nextEvent();
-            }
-            mRestartLock.lock();
-            if (mQuit) break;
-        }
-    }
-
-    mStop.clear();
-    mRestart.clear();
-    mCondition.signal();
-    mRestartLock.unlock();
-    ALOGV("goodbye");
-    return 0;
-}
-
-void SoundPool::quit()
-{
-    mRestartLock.lock();
-    mQuit = true;
-    mCondition.signal();
-    mCondition.wait(mRestartLock);
-    ALOGV("return from quit");
-    mRestartLock.unlock();
-}
-
-bool SoundPool::startThreads()
-{
-    createThreadEtc(beginThread, this, "SoundPool");
-    if (mDecodeThread == NULL)
-        mDecodeThread = new SoundPoolThread(this);
-    return mDecodeThread != NULL;
-}
-
-sp<Sample> SoundPool::findSample(int sampleID)
-{
-    Mutex::Autolock lock(&mLock);
-    return findSample_l(sampleID);
-}
-
-sp<Sample> SoundPool::findSample_l(int sampleID)
-{
-    return mSamples.valueFor(sampleID);
-}
-
-SoundChannel* SoundPool::findChannel(int channelID)
-{
-    for (int i = 0; i < mMaxChannels; ++i) {
-        if (mChannelPool[i].channelID() == channelID) {
-            return &mChannelPool[i];
-        }
-    }
-    return NULL;
-}
-
-SoundChannel* SoundPool::findNextChannel(int channelID)
-{
-    for (int i = 0; i < mMaxChannels; ++i) {
-        if (mChannelPool[i].nextChannelID() == channelID) {
-            return &mChannelPool[i];
-        }
-    }
-    return NULL;
-}
-
-int SoundPool::load(int fd, int64_t offset, int64_t length, int priority __unused)
-{
-    ALOGV("load: fd=%d, offset=%" PRId64 ", length=%" PRId64 ", priority=%d",
-            fd, offset, length, priority);
-    int sampleID;
-    {
-        Mutex::Autolock lock(&mLock);
-        sampleID = ++mNextSampleID;
-        sp<Sample> sample = new Sample(sampleID, fd, offset, length);
-        mSamples.add(sampleID, sample);
-        sample->startLoad();
-    }
-    // mDecodeThread->loadSample() must be called outside of mLock.
-    // mDecodeThread->loadSample() may block on mDecodeThread message queue space;
-    // the message queue emptying may block on SoundPool::findSample().
-    //
-    // It theoretically possible that sample loads might decode out-of-order.
-    mDecodeThread->loadSample(sampleID);
-    return sampleID;
-}
-
-bool SoundPool::unload(int sampleID)
-{
-    ALOGV("unload: sampleID=%d", sampleID);
-    Mutex::Autolock lock(&mLock);
-    return mSamples.removeItem(sampleID) >= 0; // removeItem() returns index or BAD_VALUE
-}
-
-int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
-        int priority, int loop, float rate)
-{
-    ALOGV("play sampleID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f",
-            sampleID, leftVolume, rightVolume, priority, loop, rate);
-    SoundChannel* channel;
-    int channelID;
-
-    Mutex::Autolock lock(&mLock);
-
-    if (mQuit) {
-        return 0;
-    }
-    // is sample ready?
-    sp<Sample> sample(findSample_l(sampleID));
-    if ((sample == 0) || (sample->state() != Sample::READY)) {
-        ALOGW("  sample %d not READY", sampleID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    const std::shared_ptr<soundpool::Sound> sound = mSoundManager.findSound(soundID);
+    if (sound == nullptr || sound->getState() != soundpool::Sound::READY) {
+        ALOGW("%s soundID %d not READY", __func__, soundID);
         return 0;
     }
 
-    dump();
-
-    // allocate a channel
-    channel = allocateChannel_l(priority, sampleID);
-
-    // no channel allocated - return 0
-    if (!channel) {
-        ALOGV("No channel allocated");
-        return 0;
-    }
-
-    channelID = ++mNextChannelID;
-
-    ALOGV("play channel %p state = %d", channel, channel->state());
-    channel->play(sample, channelID, leftVolume, rightVolume, priority, loop, rate);
-    return channelID;
-}
-
-SoundChannel* SoundPool::allocateChannel_l(int priority, int sampleID)
-{
-    List<SoundChannel*>::iterator iter;
-    SoundChannel* channel = NULL;
-
-    // check if channel for given sampleID still available
-    if (!mChannels.empty()) {
-        for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
-            if (sampleID == (*iter)->getPrevSampleID() && (*iter)->state() == SoundChannel::IDLE) {
-                channel = *iter;
-                mChannels.erase(iter);
-                ALOGV("Allocated recycled channel for same sampleID");
-                break;
-            }
-        }
-    }
-
-    // allocate any channel
-    if (!channel && !mChannels.empty()) {
-        iter = mChannels.begin();
-        if (priority >= (*iter)->priority()) {
-            channel = *iter;
-            mChannels.erase(iter);
-            ALOGV("Allocated active channel");
-        }
-    }
-
-    // update priority and put it back in the list
-    if (channel) {
-        channel->setPriority(priority);
-        for (iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
-            if (priority < (*iter)->priority()) {
-                break;
-            }
-        }
-        mChannels.insert(iter, channel);
-    }
-    return channel;
-}
-
-// move a channel from its current position to the front of the list
-void SoundPool::moveToFront_l(SoundChannel* channel)
-{
-    for (List<SoundChannel*>::iterator iter = mChannels.begin(); iter != mChannels.end(); ++iter) {
-        if (*iter == channel) {
-            mChannels.erase(iter);
-            mChannels.push_front(channel);
-            break;
-        }
-    }
-}
-
-void SoundPool::pause(int channelID)
-{
-    ALOGV("pause(%d)", channelID);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->pause();
-    }
+    const int32_t streamID = mStreamManager.queueForPlay(
+            sound, soundID, leftVolume, rightVolume, priority, loop, rate);
+    ALOGV("%s returned %d", __func__, streamID);
+    return streamID;
 }
 
 void SoundPool::autoPause()
 {
-    ALOGV("autoPause()");
-    Mutex::Autolock lock(&mLock);
-    for (int i = 0; i < mMaxChannels; ++i) {
-        SoundChannel* channel = &mChannelPool[i];
-        channel->autoPause();
-    }
-}
-
-void SoundPool::resume(int channelID)
-{
-    ALOGV("resume(%d)", channelID);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->resume();
-    }
-}
-
-void SoundPool::mute(bool muting)
-{
-    ALOGV("mute(%d)", muting);
-    Mutex::Autolock lock(&mLock);
-    mMuted = muting;
-    if (!mChannels.empty()) {
-            for (List<SoundChannel*>::iterator iter = mChannels.begin();
-                    iter != mChannels.end(); ++iter) {
-                (*iter)->mute(muting);
-            }
-        }
+    ALOGV("%s()", __func__);
+    auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+    mStreamManager.forEach([](soundpool::Stream *stream) { stream->autoPause(); });
 }
 
 void SoundPool::autoResume()
 {
-    ALOGV("autoResume()");
-    Mutex::Autolock lock(&mLock);
-    for (int i = 0; i < mMaxChannels; ++i) {
-        SoundChannel* channel = &mChannelPool[i];
-        channel->autoResume();
+    ALOGV("%s()", __func__);
+    auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+    mStreamManager.forEach([](soundpool::Stream *stream) { stream->autoResume(); });
+}
+
+void SoundPool::mute(bool muting)
+{
+    ALOGV("%s(%d)", __func__, muting);
+    auto apiLock = std::make_unique<std::lock_guard<std::mutex>>(mApiLock);
+    mStreamManager.forEach([=](soundpool::Stream *stream) { stream->mute(muting); });
+}
+
+void SoundPool::pause(int32_t streamID)
+{
+    ALOGV("%s(%d)", __func__, streamID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->pause(streamID);
     }
 }
 
-void SoundPool::stop(int channelID)
+void SoundPool::resume(int32_t streamID)
 {
-    ALOGV("stop(%d)", channelID);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->stop();
-    } else {
-        channel = findNextChannel(channelID);
-        if (channel)
-            channel->clearNextEvent();
+    ALOGV("%s(%d)", __func__, streamID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->resume(streamID);
     }
 }
 
-void SoundPool::setVolume(int channelID, float leftVolume, float rightVolume)
+void SoundPool::stop(int32_t streamID)
 {
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->setVolume(leftVolume, rightVolume);
+    ALOGV("%s(%d)", __func__, streamID);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    soundpool::Stream* stream = mStreamManager.findStream(streamID);
+    if (stream != nullptr && stream->requestStop(streamID)) {
+        mStreamManager.moveToRestartQueue(stream);
     }
 }
 
-void SoundPool::setPriority(int channelID, int priority)
+void SoundPool::setVolume(int32_t streamID, float leftVolume, float rightVolume)
 {
-    ALOGV("setPriority(%d, %d)", channelID, priority);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->setPriority(priority);
+    ALOGV("%s(%d, %f %f)", __func__, streamID, leftVolume, rightVolume);
+    if (checkVolume(&leftVolume, &rightVolume)) return;
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->setVolume(streamID, leftVolume, rightVolume);
     }
 }
 
-void SoundPool::setLoop(int channelID, int loop)
+void SoundPool::setPriority(int32_t streamID, int32_t priority)
 {
-    ALOGV("setLoop(%d, %d)", channelID, loop);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->setLoop(loop);
+    ALOGV("%s(%d, %d)", __func__, streamID, priority);
+    if (checkPriority(&priority)) return;
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->setPriority(streamID, priority);
     }
 }
 
-void SoundPool::setRate(int channelID, float rate)
+void SoundPool::setLoop(int32_t streamID, int32_t loop)
 {
-    ALOGV("setRate(%d, %f)", channelID, rate);
-    Mutex::Autolock lock(&mLock);
-    SoundChannel* channel = findChannel(channelID);
-    if (channel) {
-        channel->setRate(rate);
+    ALOGV("%s(%d, %d)", __func__, streamID, loop);
+    if (checkLoop(&loop)) return;
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->setLoop(streamID, loop);
     }
 }
 
-// call with lock held
-void SoundPool::done_l(SoundChannel* channel)
+void SoundPool::setRate(int32_t streamID, float rate)
 {
-    ALOGV("done_l(%d)", channel->channelID());
-    // if "stolen", play next event
-    if (channel->nextChannelID() != 0) {
-        ALOGV("add to restart list");
-        addToRestartList(channel);
-    }
-
-    // return to idle state
-    else {
-        ALOGV("move to front");
-        moveToFront_l(channel);
+    ALOGV("%s(%d, %f)", __func__, streamID, rate);
+    if (checkRate(&rate)) return;
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    if (soundpool::Stream* stream = mStreamManager.findStream(streamID)) {
+        stream->setRate(streamID, rate);
     }
 }
 
 void SoundPool::setCallback(SoundPoolCallback* callback, void* user)
 {
-    Mutex::Autolock lock(&mCallbackLock);
-    mCallback = callback;
-    mUserData = user;
+    ALOGV("%s(%p, %p)", __func__, callback, user);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    mSoundManager.setCallback(this, callback, user);
 }
 
-void SoundPool::notify(SoundPoolEvent event)
+void* SoundPool::getUserData() const
 {
-    Mutex::Autolock lock(&mCallbackLock);
-    if (mCallback != NULL) {
-        mCallback(event, this, mUserData);
-    }
-}
-
-void SoundPool::dump()
-{
-    for (int i = 0; i < mMaxChannels; ++i) {
-        mChannelPool[i].dump();
-    }
-}
-
-
-Sample::Sample(int sampleID, int fd, int64_t offset, int64_t length)
-{
-    init();
-    mSampleID = sampleID;
-    mFd = dup(fd);
-    mOffset = offset;
-    mLength = length;
-    ALOGV("create sampleID=%d, fd=%d, offset=%" PRId64 " length=%" PRId64,
-        mSampleID, mFd, mLength, mOffset);
-}
-
-void Sample::init()
-{
-    mSize = 0;
-    mRefCount = 0;
-    mSampleID = 0;
-    mState = UNLOADED;
-    mFd = -1;
-    mOffset = 0;
-    mLength = 0;
-}
-
-Sample::~Sample()
-{
-    ALOGV("Sample::destructor sampleID=%d, fd=%d", mSampleID, mFd);
-    if (mFd > 0) {
-        ALOGV("close(%d)", mFd);
-        ::close(mFd);
-    }
-}
-
-static status_t decode(int fd, int64_t offset, int64_t length,
-        uint32_t *rate, int *numChannels, audio_format_t *audioFormat,
-        audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
-        size_t *memsize) {
-
-    ALOGV("fd %d, offset %" PRId64 ", size %" PRId64, fd, offset, length);
-    AMediaExtractor *ex = AMediaExtractor_new();
-    status_t err = AMediaExtractor_setDataSourceFd(ex, fd, offset, length);
-
-    if (err != AMEDIA_OK) {
-        AMediaExtractor_delete(ex);
-        return err;
-    }
-
-    *audioFormat = AUDIO_FORMAT_PCM_16_BIT;
-
-    size_t numTracks = AMediaExtractor_getTrackCount(ex);
-    for (size_t i = 0; i < numTracks; i++) {
-        AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
-        const char *mime;
-        if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
-            AMediaExtractor_delete(ex);
-            AMediaFormat_delete(format);
-            return UNKNOWN_ERROR;
-        }
-        if (strncmp(mime, "audio/", 6) == 0) {
-
-            AMediaCodec *codec = AMediaCodec_createDecoderByType(mime);
-            if (codec == NULL
-                    || AMediaCodec_configure(codec, format,
-                            NULL /* window */, NULL /* drm */, 0 /* flags */) != AMEDIA_OK
-                    || AMediaCodec_start(codec) != AMEDIA_OK
-                    || AMediaExtractor_selectTrack(ex, i) != AMEDIA_OK) {
-                AMediaExtractor_delete(ex);
-                AMediaCodec_delete(codec);
-                AMediaFormat_delete(format);
-                return UNKNOWN_ERROR;
-            }
-
-            bool sawInputEOS = false;
-            bool sawOutputEOS = false;
-            uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
-            size_t available = heap->getSize();
-            size_t written = 0;
-
-            AMediaFormat_delete(format);
-            format = AMediaCodec_getOutputFormat(codec);
-
-            while (!sawOutputEOS) {
-                if (!sawInputEOS) {
-                    ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec, 5000);
-                    ALOGV("input buffer %zd", bufidx);
-                    if (bufidx >= 0) {
-                        size_t bufsize;
-                        uint8_t *buf = AMediaCodec_getInputBuffer(codec, bufidx, &bufsize);
-                        if (buf == nullptr) {
-                            ALOGE("AMediaCodec_getInputBuffer returned nullptr, short decode");
-                            break;
-                        }
-                        int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
-                        ALOGV("read %d", sampleSize);
-                        if (sampleSize < 0) {
-                            sampleSize = 0;
-                            sawInputEOS = true;
-                            ALOGV("EOS");
-                        }
-                        int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
-
-                        media_status_t mstatus = AMediaCodec_queueInputBuffer(codec, bufidx,
-                                0 /* offset */, sampleSize, presentationTimeUs,
-                                sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
-                        if (mstatus != AMEDIA_OK) {
-                            // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
-                            ALOGE("AMediaCodec_queueInputBuffer returned status %d, short decode",
-                                    (int)mstatus);
-                            break;
-                        }
-                        (void)AMediaExtractor_advance(ex);
-                    }
-                }
-
-                AMediaCodecBufferInfo info;
-                int status = AMediaCodec_dequeueOutputBuffer(codec, &info, 1);
-                ALOGV("dequeueoutput returned: %d", status);
-                if (status >= 0) {
-                    if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
-                        ALOGV("output EOS");
-                        sawOutputEOS = true;
-                    }
-                    ALOGV("got decoded buffer size %d", info.size);
-
-                    uint8_t *buf = AMediaCodec_getOutputBuffer(codec, status, NULL /* out_size */);
-                    if (buf == nullptr) {
-                        ALOGE("AMediaCodec_getOutputBuffer returned nullptr, short decode");
-                        break;
-                    }
-                    size_t dataSize = info.size;
-                    if (dataSize > available) {
-                        dataSize = available;
-                    }
-                    memcpy(writePos, buf + info.offset, dataSize);
-                    writePos += dataSize;
-                    written += dataSize;
-                    available -= dataSize;
-                    media_status_t mstatus = AMediaCodec_releaseOutputBuffer(
-                            codec, status, false /* render */);
-                    if (mstatus != AMEDIA_OK) {
-                        // AMEDIA_ERROR_UNKNOWN == { -ERANGE -EINVAL -EACCES }
-                        ALOGE("AMediaCodec_releaseOutputBuffer returned status %d, short decode",
-                                (int)mstatus);
-                        break;
-                    }
-                    if (available == 0) {
-                        // there might be more data, but there's no space for it
-                        sawOutputEOS = true;
-                    }
-                } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
-                    ALOGV("output buffers changed");
-                } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
-                    AMediaFormat_delete(format);
-                    format = AMediaCodec_getOutputFormat(codec);
-                    ALOGV("format changed to: %s", AMediaFormat_toString(format));
-                } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
-                    ALOGV("no output buffer right now");
-                } else if (status <= AMEDIA_ERROR_BASE) {
-                    ALOGE("decode error: %d", status);
-                    break;
-                } else {
-                    ALOGV("unexpected info code: %d", status);
-                }
-            }
-
-            (void)AMediaCodec_stop(codec);
-            (void)AMediaCodec_delete(codec);
-            (void)AMediaExtractor_delete(ex);
-            if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, (int32_t*) rate) ||
-                    !AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, numChannels)) {
-                (void)AMediaFormat_delete(format);
-                return UNKNOWN_ERROR;
-            }
-            if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_MASK,
-                    (int32_t*) channelMask)) {
-                *channelMask = AUDIO_CHANNEL_NONE;
-            }
-            (void)AMediaFormat_delete(format);
-            *memsize = written;
-            return OK;
-        }
-        (void)AMediaFormat_delete(format);
-    }
-    (void)AMediaExtractor_delete(ex);
-    return UNKNOWN_ERROR;
-}
-
-status_t Sample::doLoad()
-{
-    uint32_t sampleRate;
-    int numChannels;
-    audio_format_t format;
-    audio_channel_mask_t channelMask;
-    status_t status;
-    mHeap = new MemoryHeapBase(kDefaultHeapSize);
-
-    ALOGV("Start decode");
-    status = decode(mFd, mOffset, mLength, &sampleRate, &numChannels, &format,
-                    &channelMask, mHeap, &mSize);
-    ALOGV("close(%d)", mFd);
-    ::close(mFd);
-    mFd = -1;
-    if (status != NO_ERROR) {
-        ALOGE("Unable to load sample");
-        goto error;
-    }
-    ALOGV("pointer = %p, size = %zu, sampleRate = %u, numChannels = %d",
-          mHeap->getBase(), mSize, sampleRate, numChannels);
-
-    if (sampleRate > kMaxSampleRate) {
-       ALOGE("Sample rate (%u) out of range", sampleRate);
-       status = BAD_VALUE;
-       goto error;
-    }
-
-    if ((numChannels < 1) || (numChannels > FCC_8)) {
-        ALOGE("Sample channel count (%d) out of range", numChannels);
-        status = BAD_VALUE;
-        goto error;
-    }
-
-    mData = new MemoryBase(mHeap, 0, mSize);
-    mSampleRate = sampleRate;
-    mNumChannels = numChannels;
-    mFormat = format;
-    mChannelMask = channelMask;
-    mState = READY;
-    return NO_ERROR;
-
-error:
-    mHeap.clear();
-    return status;
-}
-
-
-void SoundChannel::init(SoundPool* soundPool)
-{
-    mSoundPool = soundPool;
-    mPrevSampleID = -1;
-}
-
-// call with sound pool lock held
-void SoundChannel::play(const sp<Sample>& sample, int nextChannelID, float leftVolume,
-        float rightVolume, int priority, int loop, float rate)
-{
-    sp<AudioTrack> oldTrack;
-    sp<AudioTrack> newTrack;
-    status_t status = NO_ERROR;
-
-    { // scope for the lock
-        Mutex::Autolock lock(&mLock);
-
-        ALOGV("SoundChannel::play %p: sampleID=%d, channelID=%d, leftVolume=%f, rightVolume=%f,"
-                " priority=%d, loop=%d, rate=%f",
-                this, sample->sampleID(), nextChannelID, leftVolume, rightVolume,
-                priority, loop, rate);
-
-        // if not idle, this voice is being stolen
-        if (mState != IDLE) {
-            ALOGV("channel %d stolen - event queued for channel %d", channelID(), nextChannelID);
-            mNextEvent.set(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
-            stop_l();
-            return;
-        }
-
-        // initialize track
-        size_t afFrameCount;
-        uint32_t afSampleRate;
-        audio_stream_type_t streamType =
-                AudioSystem::attributesToStreamType(*mSoundPool->attributes());
-        if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
-            afFrameCount = kDefaultFrameCount;
-        }
-        if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
-            afSampleRate = kDefaultSampleRate;
-        }
-        int numChannels = sample->numChannels();
-        uint32_t sampleRate = uint32_t(float(sample->sampleRate()) * rate + 0.5);
-        size_t frameCount = 0;
-
-        if (loop) {
-            const audio_format_t format = sample->format();
-            const size_t frameSize = audio_is_linear_pcm(format)
-                    ? numChannels * audio_bytes_per_sample(format) : 1;
-            frameCount = sample->size() / frameSize;
-        }
-
-#ifndef USE_SHARED_MEM_BUFFER
-        uint32_t totalFrames = (kDefaultBufferCount * afFrameCount * sampleRate) / afSampleRate;
-        // Ensure minimum audio buffer size in case of short looped sample
-        if(frameCount < totalFrames) {
-            frameCount = totalFrames;
-        }
-#endif
-
-        // check if the existing track has the same sample id.
-        if (mAudioTrack != 0 && mPrevSampleID == sample->sampleID()) {
-            // the sample rate may fail to change if the audio track is a fast track.
-            if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
-                newTrack = mAudioTrack;
-                ALOGV("reusing track %p for sample %d", mAudioTrack.get(), sample->sampleID());
-            }
-        }
-        if (newTrack == 0) {
-            // mToggle toggles each time a track is started on a given channel.
-            // The toggle is concatenated with the SoundChannel address and passed to AudioTrack
-            // as callback user data. This enables the detection of callbacks received from the old
-            // audio track while the new one is being started and avoids processing them with
-            // wrong audio audio buffer size  (mAudioBufferSize)
-            unsigned long toggle = mToggle ^ 1;
-            void *userData = (void *)((unsigned long)this | toggle);
-            audio_channel_mask_t sampleChannelMask = sample->channelMask();
-            // When sample contains a not none channel mask, use it as is.
-            // Otherwise, use channel count to calculate channel mask.
-            audio_channel_mask_t channelMask = sampleChannelMask != AUDIO_CHANNEL_NONE
-                    ? sampleChannelMask : audio_channel_out_mask_from_count(numChannels);
-
-            // do not create a new audio track if current track is compatible with sample parameters
-    #ifdef USE_SHARED_MEM_BUFFER
-            newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
-                    channelMask, sample->getIMemory(), AUDIO_OUTPUT_FLAG_FAST, callback, userData,
-                    0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
-                    AudioTrack::TRANSFER_DEFAULT,
-                    NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
-    #else
-            uint32_t bufferFrames = (totalFrames + (kDefaultBufferCount - 1)) / kDefaultBufferCount;
-            newTrack = new AudioTrack(streamType, sampleRate, sample->format(),
-                    channelMask, frameCount, AUDIO_OUTPUT_FLAG_FAST, callback, userData,
-                    bufferFrames, AUDIO_SESSION_ALLOCATE, AudioTrack::TRANSFER_DEFAULT,
-                    NULL /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/, mSoundPool->attributes());
-    #endif
-            oldTrack = mAudioTrack;
-            status = newTrack->initCheck();
-            if (status != NO_ERROR) {
-                ALOGE("Error creating AudioTrack");
-                // newTrack goes out of scope, so reference count drops to zero
-                goto exit;
-            }
-            // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
-            mToggle = toggle;
-            mAudioTrack = newTrack;
-            ALOGV("using new track %p for sample %d", newTrack.get(), sample->sampleID());
-        }
-        if (mMuted) {
-            newTrack->setVolume(0.0f, 0.0f);
-        } else {
-            newTrack->setVolume(leftVolume, rightVolume);
-        }
-        newTrack->setLoop(0, frameCount, loop);
-        mPos = 0;
-        mSample = sample;
-        mChannelID = nextChannelID;
-        mPriority = priority;
-        mLoop = loop;
-        mLeftVolume = leftVolume;
-        mRightVolume = rightVolume;
-        mNumChannels = numChannels;
-        mRate = rate;
-        clearNextEvent();
-        mState = PLAYING;
-        mAudioTrack->start();
-        mAudioBufferSize = newTrack->frameCount()*newTrack->frameSize();
-    }
-
-exit:
-    ALOGV("delete oldTrack %p", oldTrack.get());
-    if (status != NO_ERROR) {
-        mAudioTrack.clear();
-    }
-}
-
-void SoundChannel::nextEvent()
-{
-    sp<Sample> sample;
-    int nextChannelID;
-    float leftVolume;
-    float rightVolume;
-    int priority;
-    int loop;
-    float rate;
-
-    // check for valid event
-    {
-        Mutex::Autolock lock(&mLock);
-        nextChannelID = mNextEvent.channelID();
-        if (nextChannelID  == 0) {
-            ALOGV("stolen channel has no event");
-            return;
-        }
-
-        sample = mNextEvent.sample();
-        leftVolume = mNextEvent.leftVolume();
-        rightVolume = mNextEvent.rightVolume();
-        priority = mNextEvent.priority();
-        loop = mNextEvent.loop();
-        rate = mNextEvent.rate();
-    }
-
-    ALOGV("Starting stolen channel %d -> %d", channelID(), nextChannelID);
-    play(sample, nextChannelID, leftVolume, rightVolume, priority, loop, rate);
-}
-
-void SoundChannel::callback(int event, void* user, void *info)
-{
-    SoundChannel* channel = static_cast<SoundChannel*>((void *)((unsigned long)user & ~1));
-
-    channel->process(event, info, (unsigned long)user & 1);
-}
-
-void SoundChannel::process(int event, void *info, unsigned long toggle)
-{
-    //ALOGV("process(%d)", mChannelID);
-
-    Mutex::Autolock lock(&mLock);
-
-    AudioTrack::Buffer* b = NULL;
-    if (event == AudioTrack::EVENT_MORE_DATA) {
-       b = static_cast<AudioTrack::Buffer *>(info);
-    }
-
-    if (mToggle != toggle) {
-        ALOGV("process wrong toggle %p channel %d", this, mChannelID);
-        if (b != NULL) {
-            b->size = 0;
-        }
-        return;
-    }
-
-    sp<Sample> sample = mSample;
-
-//    ALOGV("SoundChannel::process event %d", event);
-
-    if (event == AudioTrack::EVENT_MORE_DATA) {
-
-        // check for stop state
-        if (b->size == 0) return;
-
-        if (mState == IDLE) {
-            b->size = 0;
-            return;
-        }
-
-        if (sample != 0) {
-            // fill buffer
-            uint8_t* q = (uint8_t*) b->i8;
-            size_t count = 0;
-
-            if (mPos < (int)sample->size()) {
-                uint8_t* p = sample->data() + mPos;
-                count = sample->size() - mPos;
-                if (count > b->size) {
-                    count = b->size;
-                }
-                memcpy(q, p, count);
-//              ALOGV("fill: q=%p, p=%p, mPos=%u, b->size=%u, count=%d", q, p, mPos, b->size,
-//                      count);
-            } else if (mPos < mAudioBufferSize) {
-                count = mAudioBufferSize - mPos;
-                if (count > b->size) {
-                    count = b->size;
-                }
-                memset(q, 0, count);
-//              ALOGV("fill extra: q=%p, mPos=%u, b->size=%u, count=%d", q, mPos, b->size, count);
-            }
-
-            mPos += count;
-            b->size = count;
-            //ALOGV("buffer=%p, [0]=%d", b->i16, b->i16[0]);
-        }
-    } else if (event == AudioTrack::EVENT_UNDERRUN || event == AudioTrack::EVENT_BUFFER_END) {
-        ALOGV("process %p channel %d event %s",
-              this, mChannelID, (event == AudioTrack::EVENT_UNDERRUN) ? "UNDERRUN" :
-                      "BUFFER_END");
-        // Only BUFFER_END should happen as we use static tracks.
-        setVolume_l(0.f, 0.f);  // set volume to 0 to indicate no need to ramp volume down.
-        mSoundPool->addToStopList(this);
-    } else if (event == AudioTrack::EVENT_LOOP_END) {
-        ALOGV("End loop %p channel %d", this, mChannelID);
-    } else if (event == AudioTrack::EVENT_NEW_IAUDIOTRACK) {
-        ALOGV("process %p channel %d NEW_IAUDIOTRACK", this, mChannelID);
-    } else {
-        ALOGW("SoundChannel::process unexpected event %d", event);
-    }
-}
-
-
-// call with lock held
-bool SoundChannel::doStop_l()
-{
-    if (mState != IDLE) {
-        ALOGV("stop");
-        if (mLeftVolume != 0.f || mRightVolume != 0.f) {
-            setVolume_l(0.f, 0.f);
-            if (mSoundPool->attributes()->usage != AUDIO_USAGE_GAME) {
-                // Since we're forcibly halting the previously playing content,
-                // we sleep here to ensure the volume is ramped down before we stop the track.
-                // Ideally the sleep time is the mixer period, or an approximation thereof
-                // (Fast vs Normal tracks are different).
-                ALOGV("sleeping: ChannelID:%d  SampleID:%d", mChannelID, mSample->sampleID());
-                std::this_thread::sleep_for(std::chrono::milliseconds(20));
-            }
-        }
-        mAudioTrack->stop();
-        mPrevSampleID = mSample->sampleID();
-        mSample.clear();
-        mState = IDLE;
-        mPriority = IDLE_PRIORITY;
-        return true;
-    }
-    return false;
-}
-
-// call with lock held and sound pool lock held
-void SoundChannel::stop_l()
-{
-    if (doStop_l()) {
-        mSoundPool->done_l(this);
-    }
-}
-
-// call with sound pool lock held
-void SoundChannel::stop()
-{
-    bool stopped;
-    {
-        Mutex::Autolock lock(&mLock);
-        stopped = doStop_l();
-    }
-
-    if (stopped) {
-        mSoundPool->done_l(this);
-    }
-}
-
-//FIXME: Pause is a little broken right now
-void SoundChannel::pause()
-{
-    Mutex::Autolock lock(&mLock);
-    if (mState == PLAYING) {
-        ALOGV("pause track");
-        mState = PAUSED;
-        mAudioTrack->pause();
-    }
-}
-
-void SoundChannel::autoPause()
-{
-    Mutex::Autolock lock(&mLock);
-    if (mState == PLAYING) {
-        ALOGV("pause track");
-        mState = PAUSED;
-        mAutoPaused = true;
-        mAudioTrack->pause();
-    }
-}
-
-void SoundChannel::resume()
-{
-    Mutex::Autolock lock(&mLock);
-    if (mState == PAUSED) {
-        ALOGV("resume track");
-        mState = PLAYING;
-        mAutoPaused = false;
-        mAudioTrack->start();
-    }
-}
-
-void SoundChannel::autoResume()
-{
-    Mutex::Autolock lock(&mLock);
-    if (mAutoPaused && (mState == PAUSED)) {
-        ALOGV("resume track");
-        mState = PLAYING;
-        mAutoPaused = false;
-        mAudioTrack->start();
-    }
-}
-
-void SoundChannel::setRate(float rate)
-{
-    Mutex::Autolock lock(&mLock);
-    if (mAudioTrack != NULL && mSample != 0) {
-        uint32_t sampleRate = uint32_t(float(mSample->sampleRate()) * rate + 0.5);
-        mAudioTrack->setSampleRate(sampleRate);
-        mRate = rate;
-    }
-}
-
-// call with lock held
-void SoundChannel::setVolume_l(float leftVolume, float rightVolume)
-{
-    mLeftVolume = leftVolume;
-    mRightVolume = rightVolume;
-    if (mAudioTrack != NULL && !mMuted)
-        mAudioTrack->setVolume(leftVolume, rightVolume);
-}
-
-void SoundChannel::setVolume(float leftVolume, float rightVolume)
-{
-    Mutex::Autolock lock(&mLock);
-    setVolume_l(leftVolume, rightVolume);
-}
-
-void SoundChannel::mute(bool muting)
-{
-    Mutex::Autolock lock(&mLock);
-    mMuted = muting;
-    if (mAudioTrack != NULL) {
-        if (mMuted) {
-            mAudioTrack->setVolume(0.0f, 0.0f);
-        } else {
-            mAudioTrack->setVolume(mLeftVolume, mRightVolume);
-        }
-    }
-}
-
-void SoundChannel::setLoop(int loop)
-{
-    Mutex::Autolock lock(&mLock);
-    if (mAudioTrack != NULL && mSample != 0) {
-        uint32_t loopEnd = mSample->size()/mNumChannels/
-            ((mSample->format() == AUDIO_FORMAT_PCM_16_BIT) ? sizeof(int16_t) : sizeof(uint8_t));
-        mAudioTrack->setLoop(0, loopEnd, loop);
-        mLoop = loop;
-    }
-}
-
-SoundChannel::~SoundChannel()
-{
-    ALOGV("SoundChannel destructor %p", this);
-    {
-        Mutex::Autolock lock(&mLock);
-        clearNextEvent();
-        doStop_l();
-    }
-    // do not call AudioTrack destructor with mLock held as it will wait for the AudioTrack
-    // callback thread to exit which may need to execute process() and acquire the mLock.
-    mAudioTrack.clear();
-}
-
-void SoundChannel::dump()
-{
-    ALOGV("mState = %d mChannelID=%d, mNumChannels=%d, mPos = %d, mPriority=%d, mLoop=%d",
-            mState, mChannelID, mNumChannels, mPos, mPriority, mLoop);
-}
-
-void SoundEvent::set(const sp<Sample>& sample, int channelID, float leftVolume,
-            float rightVolume, int priority, int loop, float rate)
-{
-    mSample = sample;
-    mChannelID = channelID;
-    mLeftVolume = leftVolume;
-    mRightVolume = rightVolume;
-    mPriority = priority;
-    mLoop = loop;
-    mRate =rate;
+    ALOGV("%s()", __func__);
+    auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
+    return mSoundManager.getUserData();
 }
 
 } // end namespace android
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 01e4faa..d5b16ef 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -14,227 +14,59 @@
  * limitations under the License.
  */
 
-#ifndef SOUNDPOOL_H_
-#define SOUNDPOOL_H_
+#pragma once
 
-#include <utils/threads.h>
-#include <utils/List.h>
-#include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-#include <media/AudioTrack.h>
-#include <binder/MemoryHeapBase.h>
-#include <binder/MemoryBase.h>
+#include "SoundManager.h"
+#include "StreamManager.h"
 
 namespace android {
 
-static const int IDLE_PRIORITY = -1;
-
-// forward declarations
-class SoundEvent;
-class SoundPoolThread;
-class SoundPool;
-
-// for queued events
-class SoundPoolEvent {
-public:
-    explicit SoundPoolEvent(int msg, int arg1=0, int arg2=0) :
-        mMsg(msg), mArg1(arg1), mArg2(arg2) {}
-    int         mMsg;
-    int         mArg1;
-    int         mArg2;
-    enum MessageType { INVALID, SAMPLE_LOADED };
-};
-
-// callback function prototype
-typedef void SoundPoolCallback(SoundPoolEvent event, SoundPool* soundPool, void* user);
-
-// tracks samples used by application
-class Sample  : public RefBase {
-public:
-    enum sample_state { UNLOADED, LOADING, READY, UNLOADING };
-    Sample(int sampleID, int fd, int64_t offset, int64_t length);
-    ~Sample();
-    int sampleID() { return mSampleID; }
-    int numChannels() { return mNumChannels; }
-    int sampleRate() { return mSampleRate; }
-    audio_format_t format() { return mFormat; }
-    audio_channel_mask_t channelMask() { return mChannelMask; }
-    size_t size() { return mSize; }
-    int state() { return mState; }
-    uint8_t* data() { return static_cast<uint8_t*>(mData->unsecurePointer()); }
-    status_t doLoad();
-    void startLoad() { mState = LOADING; }
-    sp<IMemory> getIMemory() { return mData; }
-
-private:
-    void init();
-
-    size_t               mSize;
-    volatile int32_t     mRefCount;
-    uint16_t             mSampleID;
-    uint16_t             mSampleRate;
-    uint8_t              mState;
-    uint8_t              mNumChannels;
-    audio_format_t       mFormat;
-    audio_channel_mask_t mChannelMask;
-    int                  mFd;
-    int64_t              mOffset;
-    int64_t              mLength;
-    sp<IMemory>          mData;
-    sp<MemoryHeapBase>   mHeap;
-};
-
-// stores pending events for stolen channels
-class SoundEvent
-{
-public:
-    SoundEvent() : mChannelID(0), mLeftVolume(0), mRightVolume(0),
-            mPriority(IDLE_PRIORITY), mLoop(0), mRate(0) {}
-    void set(const sp<Sample>& sample, int channelID, float leftVolume,
-            float rightVolume, int priority, int loop, float rate);
-    sp<Sample>      sample() { return mSample; }
-    int             channelID() { return mChannelID; }
-    float           leftVolume() { return mLeftVolume; }
-    float           rightVolume() { return mRightVolume; }
-    int             priority() { return mPriority; }
-    int             loop() { return mLoop; }
-    float           rate() { return mRate; }
-    void            clear() { mChannelID = 0; mSample.clear(); }
-
-protected:
-    sp<Sample>      mSample;
-    int             mChannelID;
-    float           mLeftVolume;
-    float           mRightVolume;
-    int             mPriority;
-    int             mLoop;
-    float           mRate;
-};
-
-// for channels aka AudioTracks
-class SoundChannel : public SoundEvent {
-public:
-    enum state { IDLE, RESUMING, STOPPING, PAUSED, PLAYING };
-    SoundChannel() : mState(IDLE), mNumChannels(1),
-            mPos(0), mToggle(0), mAutoPaused(false), mMuted(false) {}
-    ~SoundChannel();
-    void init(SoundPool* soundPool);
-    void play(const sp<Sample>& sample, int channelID, float leftVolume, float rightVolume,
-            int priority, int loop, float rate);
-    void setVolume_l(float leftVolume, float rightVolume);
-    void setVolume(float leftVolume, float rightVolume);
-    void mute(bool muting);
-    void stop_l();
-    void stop();
-    void pause();
-    void autoPause();
-    void resume();
-    void autoResume();
-    void setRate(float rate);
-    int state() { return mState; }
-    void setPriority(int priority) { mPriority = priority; }
-    void setLoop(int loop);
-    int numChannels() { return mNumChannels; }
-    void clearNextEvent() { mNextEvent.clear(); }
-    void nextEvent();
-    int nextChannelID() { return mNextEvent.channelID(); }
-    void dump();
-    int getPrevSampleID(void) { return mPrevSampleID; }
-
-private:
-    static void callback(int event, void* user, void *info);
-    void process(int event, void *info, unsigned long toggle);
-    bool doStop_l();
-
-    SoundPool*          mSoundPool;
-    sp<AudioTrack>      mAudioTrack;
-    SoundEvent          mNextEvent;
-    Mutex               mLock;
-    int                 mState;
-    int                 mNumChannels;
-    int                 mPos;
-    int                 mAudioBufferSize;
-    unsigned long       mToggle;
-    bool                mAutoPaused;
-    int                 mPrevSampleID;
-    bool                mMuted;
-};
-
-// application object for managing a pool of sounds
+/**
+ * Native class for Java SoundPool, manages a pool of sounds.
+ *
+ * See the Android SoundPool Java documentation for description of valid values.
+ * https://developer.android.com/reference/android/media/SoundPool
+ */
 class SoundPool {
-    friend class SoundPoolThread;
-    friend class SoundChannel;
 public:
-    SoundPool(int maxChannels, const audio_attributes_t* pAttributes);
+    SoundPool(int32_t maxStreams, const audio_attributes_t* attributes);
     ~SoundPool();
-    int load(int fd, int64_t offset, int64_t length, int priority);
-    bool unload(int sampleID);
-    int play(int sampleID, float leftVolume, float rightVolume, int priority,
-            int loop, float rate);
-    void pause(int channelID);
-    void mute(bool muting);
+
+    // SoundPool Java API support
+    int32_t load(int fd, int64_t offset, int64_t length, int32_t priority);
+    bool unload(int32_t soundID);
+    int32_t play(int32_t soundID, float leftVolume, float rightVolume, int32_t priority,
+            int32_t loop, float rate);
+    void pause(int32_t streamID);
     void autoPause();
-    void resume(int channelID);
+    void resume(int32_t streamID);
     void autoResume();
-    void stop(int channelID);
-    void setVolume(int channelID, float leftVolume, float rightVolume);
-    void setPriority(int channelID, int priority);
-    void setLoop(int channelID, int loop);
-    void setRate(int channelID, float rate);
-    const audio_attributes_t* attributes() { return &mAttributes; }
-
-    // called from SoundPoolThread
-    void sampleLoaded(int sampleID);
-    sp<Sample> findSample(int sampleID);
-
-    // called from AudioTrack thread
-    void done_l(SoundChannel* channel);
-
-    // callback function
+    void stop(int32_t streamID);
+    void setVolume(int32_t streamID, float leftVolume, float rightVolume);
+    void setPriority(int32_t streamID, int32_t priority);
+    void setLoop(int32_t streamID, int32_t loop);
+    void setRate(int32_t streamID, float rate);
     void setCallback(SoundPoolCallback* callback, void* user);
-    void* getUserData() { return mUserData; }
+    void* getUserData() const;
+
+    // not exposed in the public Java API, used for internal playerSetVolume() muting.
+    void mute(bool muting);
 
 private:
-    SoundPool() {} // no default constructor
-    bool startThreads();
-    sp<Sample> findSample_l(int sampleID);
-    SoundChannel* findChannel (int channelID);
-    SoundChannel* findNextChannel (int channelID);
-    SoundChannel* allocateChannel_l(int priority, int sampleID);
-    void moveToFront_l(SoundChannel* channel);
-    void notify(SoundPoolEvent event);
-    void dump();
 
-    // restart thread
-    void addToRestartList(SoundChannel* channel);
-    void addToStopList(SoundChannel* channel);
-    static int beginThread(void* arg);
-    int run();
-    void quit();
+    // Constructor initialized variables
+    // Can access without lock as they are internally locked,
+    // though care needs to be taken that the final result composed of
+    // individually consistent actions are consistent.
+    soundpool::SoundManager  mSoundManager;
+    soundpool::StreamManager mStreamManager;
 
-    Mutex                   mLock;
-    Mutex                   mRestartLock;
-    Condition               mCondition;
-    SoundPoolThread*        mDecodeThread;
-    SoundChannel*           mChannelPool;
-    List<SoundChannel*>     mChannels;
-    List<SoundChannel*>     mRestart;
-    List<SoundChannel*>     mStop;
-    DefaultKeyedVector< int, sp<Sample> >   mSamples;
-    int                     mMaxChannels;
-    audio_attributes_t      mAttributes;
-    int                     mAllocated;
-    int                     mNextSampleID;
-    int                     mNextChannelID;
-    bool                    mQuit;
-    bool                    mMuted;
-
-    // callback
-    Mutex                   mCallbackLock;
-    SoundPoolCallback*      mCallback;
-    void*                   mUserData;
+    // mApiLock serializes SoundPool application calls (configurable by kUseApiLock).
+    // It only locks at the SoundPool layer and not below.  At this level,
+    // mApiLock is only required for autoPause() and autoResume() to prevent zippering
+    // of the individual pauses and resumes, and mute() for self-interaction with itself.
+    // It is optional for all other apis.
+    mutable std::mutex        mApiLock;
 };
 
 } // end namespace android
-
-#endif /*SOUNDPOOL_H_*/
diff --git a/media/jni/soundpool/SoundPoolThread.cpp b/media/jni/soundpool/SoundPoolThread.cpp
deleted file mode 100644
index ba3b482..0000000
--- a/media/jni/soundpool/SoundPoolThread.cpp
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "SoundPoolThread"
-#include "utils/Log.h"
-
-#include "SoundPoolThread.h"
-
-namespace android {
-
-void SoundPoolThread::write(SoundPoolMsg msg) {
-    Mutex::Autolock lock(&mLock);
-    while (mMsgQueue.size() >= maxMessages) {
-        mCondition.wait(mLock);
-    }
-
-    // if thread is quitting, don't add to queue
-    if (mRunning) {
-        mMsgQueue.push(msg);
-        mCondition.signal();
-    }
-}
-
-const SoundPoolMsg SoundPoolThread::read() {
-    Mutex::Autolock lock(&mLock);
-    while (mMsgQueue.size() == 0) {
-        mCondition.wait(mLock);
-    }
-    SoundPoolMsg msg = mMsgQueue[0];
-    mMsgQueue.removeAt(0);
-    mCondition.signal();
-    return msg;
-}
-
-void SoundPoolThread::quit() {
-    Mutex::Autolock lock(&mLock);
-    if (mRunning) {
-        mRunning = false;
-        mMsgQueue.clear();
-        mMsgQueue.push(SoundPoolMsg(SoundPoolMsg::KILL, 0));
-        mCondition.signal();
-        mCondition.wait(mLock);
-    }
-    ALOGV("return from quit");
-}
-
-SoundPoolThread::SoundPoolThread(SoundPool* soundPool) :
-    mSoundPool(soundPool)
-{
-    mMsgQueue.setCapacity(maxMessages);
-    if (createThreadEtc(beginThread, this, "SoundPoolThread")) {
-        mRunning = true;
-    }
-}
-
-SoundPoolThread::~SoundPoolThread()
-{
-    quit();
-}
-
-int SoundPoolThread::beginThread(void* arg) {
-    ALOGV("beginThread");
-    SoundPoolThread* soundPoolThread = (SoundPoolThread*)arg;
-    return soundPoolThread->run();
-}
-
-int SoundPoolThread::run() {
-    ALOGV("run");
-    for (;;) {
-        SoundPoolMsg msg = read();
-        ALOGV("Got message m=%d, mData=%d", msg.mMessageType, msg.mData);
-        switch (msg.mMessageType) {
-        case SoundPoolMsg::KILL:
-            ALOGV("goodbye");
-            return NO_ERROR;
-        case SoundPoolMsg::LOAD_SAMPLE:
-            doLoadSample(msg.mData);
-            break;
-        default:
-            ALOGW("run: Unrecognized message %d\n",
-                    msg.mMessageType);
-            break;
-        }
-    }
-}
-
-void SoundPoolThread::loadSample(int sampleID) {
-    write(SoundPoolMsg(SoundPoolMsg::LOAD_SAMPLE, sampleID));
-}
-
-void SoundPoolThread::doLoadSample(int sampleID) {
-    sp <Sample> sample = mSoundPool->findSample(sampleID);
-    status_t status = -1;
-    if (sample != 0) {
-        status = sample->doLoad();
-    }
-    mSoundPool->notify(SoundPoolEvent(SoundPoolEvent::SAMPLE_LOADED, sampleID, status));
-}
-
-} // end namespace android
diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/jni/soundpool/SoundPoolThread.h
deleted file mode 100644
index 7b3e1dd..0000000
--- a/media/jni/soundpool/SoundPoolThread.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SOUNDPOOLTHREAD_H_
-#define SOUNDPOOLTHREAD_H_
-
-#include <utils/threads.h>
-#include <utils/Vector.h>
-#include <media/AudioTrack.h>
-
-#include "SoundPool.h"
-
-namespace android {
-
-class SoundPoolMsg {
-public:
-    enum MessageType { INVALID, KILL, LOAD_SAMPLE };
-    SoundPoolMsg() : mMessageType(INVALID), mData(0) {}
-    SoundPoolMsg(MessageType MessageType, int data) :
-        mMessageType(MessageType), mData(data) {}
-    uint16_t         mMessageType;
-    uint16_t         mData;
-};
-
-/*
- * This class handles background requests from the SoundPool
- */
-class SoundPoolThread {
-public:
-    explicit SoundPoolThread(SoundPool* SoundPool);
-    ~SoundPoolThread();
-    void loadSample(int sampleID);
-    void quit();
-    void write(SoundPoolMsg msg);
-
-private:
-    static const size_t maxMessages = 128;
-
-    static int beginThread(void* arg);
-    int run();
-    void doLoadSample(int sampleID);
-    const SoundPoolMsg read();
-
-    Mutex                   mLock;
-    Condition               mCondition;
-    Vector<SoundPoolMsg>    mMsgQueue;
-    SoundPool*              mSoundPool;
-    bool                    mRunning;
-};
-
-} // end namespace android
-
-#endif /*SOUNDPOOLTHREAD_H_*/
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
new file mode 100644
index 0000000..e7d4d90
--- /dev/null
+++ b/media/jni/soundpool/Stream.cpp
@@ -0,0 +1,448 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::Stream"
+#include <utils/Log.h>
+
+#include "Stream.h"
+
+#include "StreamManager.h"
+
+namespace android::soundpool {
+
+Stream::~Stream()
+{
+    ALOGV("%s(%p)", __func__, this);
+}
+
+void Stream::autoPause()
+{
+    std::lock_guard lock(mLock);
+    if (mState == PLAYING) {
+        ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
+        mState = PAUSED;
+        mAutoPaused = true;
+        if (mAudioTrack != nullptr) {
+            mAudioTrack->pause();
+        }
+    }
+}
+
+void Stream::autoResume()
+{
+    std::lock_guard lock(mLock);
+    if (mAutoPaused) {
+        if (mState == PAUSED) {
+            ALOGV("%s: track streamID: %d", __func__, (int)mStreamID);
+            mState = PLAYING;
+            if (mAudioTrack != nullptr) {
+                mAudioTrack->start();
+            }
+        }
+        mAutoPaused = false; // New for R: always reset autopause (consistent with API spec).
+    }
+}
+
+void Stream::mute(bool muting)
+{
+    std::lock_guard lock(mLock);
+    mMuted = muting;
+    if (mAudioTrack != nullptr) {
+        if (mMuted) {
+            mAudioTrack->setVolume(0.0f, 0.0f);
+        } else {
+            mAudioTrack->setVolume(mLeftVolume, mRightVolume);
+        }
+    }
+}
+
+void Stream::pause(int32_t streamID)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        if (mState == PLAYING) {
+            ALOGV("%s: track streamID: %d", __func__, streamID);
+            mState = PAUSED;
+            if (mAudioTrack != nullptr) {
+                mAudioTrack->pause();
+            }
+        }
+    }
+}
+
+void Stream::resume(int32_t streamID)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+         if (mState == PAUSED) {
+            ALOGV("%s: track streamID: %d", __func__, streamID);
+            mState = PLAYING;
+            if (mAudioTrack != nullptr) {
+                mAudioTrack->start();
+            }
+            mAutoPaused = false; // TODO: is this right? (ambiguous per spec), move outside?
+        }
+    }
+}
+
+void Stream::setRate(int32_t streamID, float rate)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        mRate = rate;
+        if (mAudioTrack != nullptr && mSound != nullptr) {
+            const uint32_t sampleRate = uint32_t(float(mSound->getSampleRate()) * rate + 0.5);
+            mAudioTrack->setSampleRate(sampleRate);
+        }
+    }
+}
+
+void Stream::setVolume_l(float leftVolume, float rightVolume)
+{
+    mLeftVolume = leftVolume;
+    mRightVolume = rightVolume;
+    if (mAudioTrack != nullptr && !mMuted) {
+        mAudioTrack->setVolume(leftVolume, rightVolume);
+    }
+}
+
+void Stream::setVolume(int32_t streamID, float leftVolume, float rightVolume)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        setVolume_l(leftVolume, rightVolume);
+    }
+}
+
+void Stream::setPriority(int32_t streamID, int32_t priority)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        mPriority = priority;
+    }
+}
+
+void Stream::setLoop(int32_t streamID, int32_t loop)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        if (mAudioTrack != nullptr && mSound != nullptr) {
+            const uint32_t loopEnd = mSound->getSizeInBytes() / mSound->getChannelCount() /
+                (mSound->getFormat() == AUDIO_FORMAT_PCM_16_BIT
+                        ? sizeof(int16_t) : sizeof(uint8_t));
+            mAudioTrack->setLoop(0, loopEnd, loop);
+        }
+        mLoop = loop;
+    }
+}
+
+void Stream::setPlay(
+        int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
+        float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate)
+{
+    std::lock_guard lock(mLock);
+    // We must be idle, or we must be repurposing a pending Stream.
+    LOG_ALWAYS_FATAL_IF(mState != IDLE && mAudioTrack != nullptr, "State %d must be IDLE", mState);
+    mSound = sound;
+    mSoundID = soundID;
+    mLeftVolume = leftVolume;
+    mRightVolume = rightVolume;
+    mPriority = priority;
+    mLoop = loop;
+    mRate = rate;
+    mState = PLAYING;
+    mAutoPaused = false;   // New for R (consistent with Java API spec).
+    mStreamID = streamID;  // prefer this to be the last, as it is an atomic sync point
+}
+
+void Stream::setStopTimeNs(int64_t stopTimeNs)
+{
+    std::lock_guard lock(mLock);
+    mStopTimeNs = stopTimeNs;
+}
+
+bool Stream::requestStop(int32_t streamID)
+{
+    std::lock_guard lock(mLock);
+    if (streamID == mStreamID) {
+        if (mAudioTrack != nullptr) {
+            if (mState == PLAYING && !mMuted && (mLeftVolume != 0.f || mRightVolume != 0.f)) {
+                setVolume_l(0.f, 0.f);
+                mStopTimeNs = systemTime() + kStopWaitTimeNs;
+            } else {
+                mStopTimeNs = systemTime();
+            }
+            return true; // must be queued on the restart list.
+        }
+        stop_l();
+    }
+    return false;
+}
+
+void Stream::stop()
+{
+    std::lock_guard lock(mLock);
+    stop_l();
+}
+
+void Stream::stop_l()
+{
+    if (mState != IDLE) {
+        if (mAudioTrack != nullptr) {
+            mAudioTrack->stop();
+        }
+        mSound.reset();
+        mState = IDLE;
+    }
+}
+
+void Stream::clearAudioTrack()
+{
+    // This will invoke the destructor which waits for the AudioTrack thread to join,
+    // and is currently the only safe way to ensure there are no callbacks afterwards.
+    mAudioTrack.clear();
+}
+
+Stream* Stream::getPairStream() const
+{
+   return mStreamManager->getPairStream(this);
+}
+
+Stream* Stream::playPairStream() {
+    Stream* pairStream = getPairStream();
+    LOG_ALWAYS_FATAL_IF(pairStream == nullptr, "No pair stream!");
+    sp<AudioTrack> releaseTracks[2];
+    {
+        // TODO: Do we really want to force a simultaneous synchronization between
+        // the stream and its pair?
+
+        // note locking order - the paired stream is obtained before the queued stream.
+        // we can invert the locking order, but it is slightly more optimal to do it this way.
+        std::lock_guard lockp(pairStream->mLock);
+        if (pairStream->mSound == nullptr) {
+            return nullptr; // no pair sound
+        }
+        {
+            std::lock_guard lock(mLock);
+            LOG_ALWAYS_FATAL_IF(mState != IDLE, "State: %d must be IDLE", mState);
+            // TODO: do we want a specific set() here?
+            pairStream->mAudioTrack = mAudioTrack;
+            pairStream->mSoundID = mSoundID; // optimization to reuse AudioTrack.
+            pairStream->mToggle = mToggle;
+            pairStream->mAutoPaused = mAutoPaused; // save autopause state
+            pairStream->mMuted = mMuted;
+            mAudioTrack.clear();  // the pair owns the audiotrack.
+            mSound.reset();
+            mSoundID = 0;
+        }
+        // TODO: do we need a specific play_l() anymore?
+        const int pairState = pairStream->mState;
+        pairStream->play_l(pairStream->mSound, pairStream->mStreamID,
+                pairStream->mLeftVolume, pairStream->mRightVolume, pairStream->mPriority,
+                pairStream->mLoop, pairStream->mRate, releaseTracks);
+        if (pairStream->mState == IDLE) {
+            return nullptr; // AudioTrack error
+        }
+        if (pairState == PAUSED) {  // reestablish pause
+            pairStream->mState = PAUSED;
+            pairStream->mAudioTrack->pause();
+        }
+    }
+    // release tracks outside of Stream lock
+    return pairStream;
+}
+
+void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
+        float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
+        sp<AudioTrack> releaseTracks[2])
+{
+    // These tracks are released without the lock.
+    sp<AudioTrack> &oldTrack = releaseTracks[0];
+    sp<AudioTrack> &newTrack = releaseTracks[1];
+    status_t status = NO_ERROR;
+
+    {
+        ALOGV("%s(%p)(soundID=%d, streamID=%d, leftVolume=%f, rightVolume=%f,"
+                " priority=%d, loop=%d, rate=%f)",
+                __func__, this, sound->getSoundID(), nextStreamID, leftVolume, rightVolume,
+                priority, loop, rate);
+
+        // initialize track
+        const audio_stream_type_t streamType =
+                AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
+        const int32_t channelCount = sound->getChannelCount();
+        const uint32_t sampleRate = uint32_t(float(sound->getSampleRate()) * rate + 0.5);
+        size_t frameCount = 0;
+
+        if (loop) {
+            const audio_format_t format = sound->getFormat();
+            const size_t frameSize = audio_is_linear_pcm(format)
+                    ? channelCount * audio_bytes_per_sample(format) : 1;
+            frameCount = sound->getSizeInBytes() / frameSize;
+        }
+
+        // check if the existing track has the same sound id.
+        if (mAudioTrack != nullptr && mSoundID == sound->getSoundID()) {
+            // the sample rate may fail to change if the audio track is a fast track.
+            if (mAudioTrack->setSampleRate(sampleRate) == NO_ERROR) {
+                newTrack = mAudioTrack;
+                ALOGV("%s: reusing track %p for sound %d",
+                        __func__, mAudioTrack.get(), sound->getSoundID());
+            }
+        }
+        if (newTrack == 0) {
+            // mToggle toggles each time a track is started on a given stream.
+            // The toggle is concatenated with the Stream address and passed to AudioTrack
+            // as callback user data. This enables the detection of callbacks received from the old
+            // audio track while the new one is being started and avoids processing them with
+            // wrong audio audio buffer size  (mAudioBufferSize)
+            auto toggle = mToggle ^ 1;
+            void* userData = (void*)((uintptr_t)this | toggle);
+            audio_channel_mask_t soundChannelMask = sound->getChannelMask();
+            // When sound contains a valid channel mask, use it as is.
+            // Otherwise, use stream count to calculate channel mask.
+            audio_channel_mask_t channelMask = soundChannelMask != AUDIO_CHANNEL_NONE
+                    ? soundChannelMask : audio_channel_out_mask_from_count(channelCount);
+
+            // do not create a new audio track if current track is compatible with sound parameters
+
+            newTrack = new AudioTrack(streamType, sampleRate, sound->getFormat(),
+                    channelMask, sound->getIMemory(), AUDIO_OUTPUT_FLAG_FAST,
+                    staticCallback, userData,
+                    0 /*default notification frames*/, AUDIO_SESSION_ALLOCATE,
+                    AudioTrack::TRANSFER_DEFAULT,
+                    nullptr /*offloadInfo*/, -1 /*uid*/, -1 /*pid*/,
+                    mStreamManager->getAttributes());
+
+            oldTrack = mAudioTrack;
+            status = newTrack->initCheck();
+            if (status != NO_ERROR) {
+                ALOGE("%s: error creating AudioTrack", __func__);
+                // newTrack goes out of scope, so reference count drops to zero
+                goto exit;
+            }
+            // From now on, AudioTrack callbacks received with previous toggle value will be ignored.
+            mToggle = toggle;
+            mAudioTrack = newTrack;
+            ALOGV("%s: using new track %p for sound %d",
+                    __func__, newTrack.get(), sound->getSoundID());
+        }
+        if (mMuted) {
+            newTrack->setVolume(0.0f, 0.0f);
+        } else {
+            newTrack->setVolume(leftVolume, rightVolume);
+        }
+        newTrack->setLoop(0, frameCount, loop);
+        mAudioTrack->start();
+        mSound = sound;
+        mSoundID = sound->getSoundID();
+        mPriority = priority;
+        mLoop = loop;
+        mLeftVolume = leftVolume;
+        mRightVolume = rightVolume;
+        mRate = rate;
+        mState = PLAYING;
+        mStopTimeNs = 0;
+        mStreamID = nextStreamID;  // prefer this to be the last, as it is an atomic sync point
+    }
+
+exit:
+    ALOGV("%s: delete oldTrack %p", __func__, oldTrack.get());
+    if (status != NO_ERROR) {
+        // TODO: should we consider keeping the soundID if the old track is OK?
+        // Do not attempt to restart this track (should we remove the stream id?)
+        mState = IDLE;
+        mSoundID = 0;
+        mSound.reset();
+        mAudioTrack.clear();  // actual release from releaseTracks[]
+    }
+}
+
+/* static */
+void Stream::staticCallback(int event, void* user, void* info)
+{
+    const uintptr_t userAsInt = (uintptr_t)user;
+    Stream* stream = reinterpret_cast<Stream*>(userAsInt & ~1);
+    stream->callback(event, info, userAsInt & 1, 0 /* tries */);
+}
+
+void Stream::callback(int event, void* info, int toggle, int tries)
+{
+    ALOGV("%s streamID %d", __func__, (int)mStreamID);
+    int32_t activeStreamIDToRestart = 0;
+    {
+        std::unique_lock lock(mLock);
+
+        if (mAudioTrack == nullptr) {
+            // The AudioTrack is either with this stream or its pair.
+            // if this swaps a few times, the toggle is bound to be wrong, so we fail then.
+            //
+            // TODO: Modify AudioTrack callbacks to avoid the hacky toggle and retry
+            // logic here.
+            if (tries < 3) {
+                lock.unlock();
+                getPairStream()->callback(event, info, toggle, tries + 1);
+            } else {
+                ALOGW("%s streamID %d cannot find track", __func__, (int)mStreamID);
+            }
+            return;
+        }
+        if (mToggle != toggle) {
+            ALOGD("%s streamID %d wrong toggle", __func__, (int)mStreamID);
+            return;
+        }
+        switch (event) {
+        case AudioTrack::EVENT_MORE_DATA:
+            ALOGW("%s streamID %d Invalid EVENT_MORE_DATA for static track",
+                    __func__, (int)mStreamID);
+            break;
+        case AudioTrack::EVENT_UNDERRUN:
+            ALOGW("%s streamID %d Invalid EVENT_UNDERRUN for static track",
+                    __func__, (int)mStreamID);
+            break;
+        case AudioTrack::EVENT_BUFFER_END:
+            ALOGV("%s streamID %d EVENT_BUFFER_END", __func__, (int)mStreamID);
+            if (mState != IDLE) {
+                activeStreamIDToRestart = mStreamID;
+                mStopTimeNs = systemTime();
+            }
+            break;
+        case AudioTrack::EVENT_LOOP_END:
+            ALOGV("%s streamID %d EVENT_LOOP_END", __func__, (int)mStreamID);
+            break;
+        case AudioTrack::EVENT_NEW_IAUDIOTRACK:
+            ALOGV("%s streamID %d NEW_IAUDIOTRACK", __func__, (int)mStreamID);
+            break;
+        default:
+            ALOGW("%s streamID %d Invalid event %d", __func__, (int)mStreamID, event);
+            break;
+        }
+    } // lock ends here.  This is on the callback thread, no need to be precise.
+    if (activeStreamIDToRestart > 0) {
+        // Restart only if a particular streamID is still current and active.
+        ALOGV("%s: moveToRestartQueue %d", __func__, activeStreamIDToRestart);
+        mStreamManager->moveToRestartQueue(this, activeStreamIDToRestart);
+    }
+}
+
+void Stream::dump() const
+{
+    ALOGV("mPairStream=%p, mState=%d, mStreamID=%d, mSoundID=%d, mPriority=%d, mLoop=%d",
+            getPairStream(), mState, (int)mStreamID, mSoundID, mPriority, mLoop);
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
new file mode 100644
index 0000000..82d2690
--- /dev/null
+++ b/media/jni/soundpool/Stream.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "Sound.h"
+
+#include <audio_utils/clock.h>
+#include <media/AudioTrack.h>
+
+namespace android::soundpool {
+
+// This is the amount of time to wait after stop is called when stealing an
+// AudioTrack to allow the sound to ramp down.  If this is 0, glitches
+// may occur when stealing an AudioTrack.
+inline constexpr int64_t kStopWaitTimeNs = 20 * NANOS_PER_MILLISECOND;
+
+inline constexpr size_t kCacheLineSize = 64; /* std::hardware_constructive_interference_size */
+
+class StreamManager; // forward decl
+
+/**
+ * A Stream is associated with a StreamID exposed to the app to play a Sound.
+ *
+ * The Stream uses monitor locking strategy on mLock.
+ * https://en.wikipedia.org/wiki/Monitor_(synchronization)
+ *
+ * where public methods are guarded by a lock (as needed)
+ *
+ * For Java equivalent APIs, see
+ * https://developer.android.com/reference/android/media/SoundPool
+ *
+ * Streams are paired by the StreamManager, so one stream in the pair may be "stopping"
+ * while the other stream of the pair has been prepared to run
+ * (and the streamID returned to the app) pending its pair to be stopped.
+ * The pair of a Stream may be obtained by calling getPairStream(),
+ * where this->getPairStream()->getPairStream() == this; (pair is a commutative relationship).
+ *
+ * playPairStream() and getPairPriority() access the paired stream.
+ * See also StreamManager.h for details of physical layout implications of paired streams.
+ */
+class alignas(kCacheLineSize) Stream {
+public:
+    enum state { IDLE, PAUSED, PLAYING };
+    // The PAUSED, PLAYING state directly corresponds to the AudioTrack state of an active Stream.
+    //
+    // The IDLE state indicates an inactive Stream.   An IDLE Stream may have a non-nullptr
+    // AudioTrack, which may be recycled for use if the SoundID matches the next Stream playback.
+    //
+    // PAUSED -> PLAYING through resume()  (see also autoResume())
+    // PLAYING -> PAUSED through pause()   (see also autoPause())
+    //
+    // IDLE is the initial state of a Stream and also when a stream becomes inactive.
+    // {PAUSED, PLAYING} -> IDLE through stop() (or if the Sound finishes playing)
+    // IDLE -> PLAYING through play().  (there is no way to start a Stream in paused mode).
+
+    ~Stream();
+    void setStreamManager(StreamManager* streamManager) { // non-nullptr
+        mStreamManager = streamManager; // set in StreamManager constructor, not changed
+    }
+
+    // The following methods are monitor locked by mLock.
+    //
+    // For methods taking a streamID:
+    // if the streamID matches the Stream's mStreamID, then method proceeds
+    // else the command is ignored with no effect.
+
+    // returns true if the stream needs to be explicitly stopped.
+    bool requestStop(int32_t streamID);
+    void stop();                    // explicit stop(), typically called from the worker thread.
+    void clearAudioTrack();
+    void pause(int32_t streamID);
+    void autoPause();               // see the Java SoundPool.autoPause documentation for details.
+    void resume(int32_t streamID);
+    void autoResume();
+    void mute(bool muting);
+    void dump() const;
+
+    // returns the pair stream if successful, nullptr otherwise
+    Stream* playPairStream();
+
+    // These parameters are explicitly checked in the SoundPool class
+    // so never deviate from the Java API specified values.
+    void setVolume(int32_t streamID, float leftVolume, float rightVolume);
+    void setRate(int32_t streamID, float rate);
+    void setPriority(int32_t streamID, int priority);
+    void setLoop(int32_t streamID, int loop);
+    void setPlay(int32_t streamID, const std::shared_ptr<Sound> &sound, int32_t soundID,
+           float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate);
+    void setStopTimeNs(int64_t stopTimeNs); // systemTime() clock monotonic.
+
+    // The following getters are not locked and have weak consistency.
+    // These are considered advisory only - being stale is of nuisance.
+    int32_t getPriority() const { return mPriority; }
+    int32_t getPairPriority() const { return getPairStream()->getPriority(); }
+    int64_t getStopTimeNs() const { return mStopTimeNs; }
+
+    int32_t getStreamID() const { return mStreamID; }  // Can change with setPlay()
+    int32_t getSoundID() const { return mSoundID; }    // Can change with play_l()
+    bool hasSound() const { return mSound.get() != nullptr; }
+
+    Stream* getPairStream() const;  // this never changes.  See top of header.
+
+private:
+    void play_l(const std::shared_ptr<Sound>& sound, int streamID,
+            float leftVolume, float rightVolume, int priority, int loop, float rate,
+            sp<AudioTrack> releaseTracks[2]);
+    void stop_l();
+    void setVolume_l(float leftVolume, float rightVolume);
+
+    // For use with AudioTrack callback.
+    static void staticCallback(int event, void* user, void* info);
+    void callback(int event, void* info, int toggle, int tries);
+
+    // StreamManager should be set on construction and not changed.
+    // release mLock before calling into StreamManager
+    StreamManager*     mStreamManager = nullptr;
+
+    mutable std::mutex  mLock;
+    std::atomic_int32_t mStreamID = 0;          // Note: valid streamIDs are always positive.
+    int                 mState = IDLE;
+    std::shared_ptr<Sound> mSound;              // Non-null if playing.
+    int32_t             mSoundID = 0;           // The sound ID associated with the AudioTrack.
+    float               mLeftVolume = 0.f;
+    float               mRightVolume = 0.f;
+    int32_t             mPriority = INT32_MIN;
+    int32_t             mLoop = 0;
+    float               mRate = 0.f;
+    bool                mAutoPaused = false;
+    bool                mMuted = false;
+
+    sp<AudioTrack>      mAudioTrack;
+    int                 mToggle = 0;
+    int64_t             mStopTimeNs = 0;        // if nonzero, time to wait for stop.
+};
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
new file mode 100644
index 0000000..8928c47
--- /dev/null
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -0,0 +1,407 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoundPool::StreamManager"
+#include <utils/Log.h>
+
+#include "StreamManager.h"
+
+#include <audio_utils/clock.h>
+#include <audio_utils/roundup.h>
+
+namespace android::soundpool {
+
+// kMaxStreams is number that should be less than the current AudioTrack max per UID of 40.
+// It is the maximum number of AudioTrack resources allowed in the SoundPool.
+// We suggest a value at least 4 or greater to allow CTS tests to pass.
+static constexpr int32_t kMaxStreams = 32;
+
+// kStealActiveStream_OldestFirst = false historically (Q and earlier)
+// Changing to true could break app expectations but could change behavior beneficially.
+// In R, we change this to true, as it is the correct way per SoundPool documentation.
+static constexpr bool kStealActiveStream_OldestFirst = true;
+
+// kPlayOnCallingThread = true prior to R.
+// Changing to false means calls to play() are almost instantaneous instead of taking around
+// ~10ms to launch the AudioTrack. It is perhaps 100x faster.
+static constexpr bool kPlayOnCallingThread = false;
+
+// Amount of time for a StreamManager thread to wait before closing.
+static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
+
+////////////
+
+StreamMap::StreamMap(int32_t streams) {
+    ALOGV("%s(%d)", __func__, streams);
+    if (streams > kMaxStreams) {
+        ALOGW("%s: requested %d streams, clamping to %d", __func__, streams, kMaxStreams);
+        streams = kMaxStreams;
+    } else if (streams < 1) {
+        ALOGW("%s: requested %d streams, clamping to 1", __func__, streams);
+        streams = 1;
+    }
+    mStreamPoolSize = streams * 2;
+    mStreamPool.reset(new Stream[mStreamPoolSize]);
+    // we use a perfect hash table with 2x size to map StreamIDs to Stream pointers.
+    mPerfectHash = std::make_unique<PerfectHash<int32_t, Stream *>>(roundup(mStreamPoolSize * 2));
+}
+
+Stream* StreamMap::findStream(int32_t streamID) const
+{
+    Stream *stream = lookupStreamFromId(streamID);
+    return stream != nullptr && stream->getStreamID() == streamID ? stream : nullptr;
+}
+
+size_t StreamMap::streamPosition(const Stream* stream) const
+{
+    ptrdiff_t index = stream - mStreamPool.get();
+    LOG_ALWAYS_FATAL_IF(index < 0 || index >= mStreamPoolSize,
+            "%s: stream position out of range: %td", __func__, index);
+    return (size_t)index;
+}
+
+Stream* StreamMap::lookupStreamFromId(int32_t streamID) const
+{
+    return streamID > 0 ? mPerfectHash->getValue(streamID).load() : nullptr;
+}
+
+int32_t StreamMap::getNextIdForStream(Stream* stream) const {
+    // even though it is const, it mutates the internal hash table.
+    const int32_t id = mPerfectHash->generateKey(
+        stream,
+        [] (Stream *stream) {
+            return stream == nullptr ? 0 : stream->getStreamID();
+        }, /* getKforV() */
+        stream->getStreamID() /* oldID */);
+    return id;
+}
+
+////////////
+
+StreamManager::StreamManager(
+        int32_t streams, size_t threads, const audio_attributes_t* attributes)
+    : StreamMap(streams)
+    , mAttributes(*attributes)
+{
+    ALOGV("%s(%d, %zu, ...)", __func__, streams, threads);
+    forEach([this](Stream *stream) {
+        stream->setStreamManager(this);
+        if ((streamPosition(stream) & 1) == 0) { // put the first stream of pair as available.
+            mAvailableStreams.insert(stream);
+        }
+    });
+
+    mThreadPool = std::make_unique<ThreadPool>(
+            std::min(threads, (size_t)std::thread::hardware_concurrency()),
+            "SoundPool_");
+}
+
+StreamManager::~StreamManager()
+{
+    ALOGV("%s", __func__);
+    {
+        std::unique_lock lock(mStreamManagerLock);
+        mQuit = true;
+        mStreamManagerCondition.notify_all();
+    }
+    mThreadPool->quit();
+
+    // call stop on the stream pool
+    forEach([](Stream *stream) { stream->stop(); });
+
+    // This invokes the destructor on the AudioTracks -
+    // we do it here to ensure that AudioTrack callbacks will not occur
+    // afterwards.
+    forEach([](Stream *stream) { stream->clearAudioTrack(); });
+}
+
+
+int32_t StreamManager::queueForPlay(const std::shared_ptr<Sound> &sound,
+        int32_t soundID, float leftVolume, float rightVolume,
+        int32_t priority, int32_t loop, float rate)
+{
+    ALOGV("%s(sound=%p, soundID=%d, leftVolume=%f, rightVolume=%f, priority=%d, loop=%d, rate=%f)",
+            __func__, sound.get(), soundID, leftVolume, rightVolume, priority, loop, rate);
+    bool launchThread = false;
+    int32_t streamID = 0;
+
+    { // for lock
+        std::unique_lock lock(mStreamManagerLock);
+        Stream *newStream = nullptr;
+        bool fromAvailableQueue = false;
+        ALOGV("%s: mStreamManagerLock lock acquired", __func__);
+
+        sanityCheckQueue_l();
+        // find an available stream, prefer one that has matching sound id.
+        if (mAvailableStreams.size() > 0) {
+            newStream = *mAvailableStreams.begin();
+            for (auto stream : mAvailableStreams) {
+                if (stream->getSoundID() == soundID) {
+                    newStream = stream;
+                    break;
+                }
+            }
+            if (newStream != nullptr) {
+                newStream->setStopTimeNs(systemTime());
+            }
+            fromAvailableQueue = true;
+        }
+
+        // also look in the streams restarting (if the paired stream doesn't have a pending play)
+        if (newStream == nullptr || newStream->getSoundID() != soundID) {
+            for (auto [unused , stream] : mRestartStreams) {
+                if (!stream->getPairStream()->hasSound()) {
+                    if (stream->getSoundID() == soundID) {
+                        newStream = stream;
+                        break;
+                    } else if (newStream == nullptr) {
+                        newStream = stream;
+                    }
+                }
+            }
+        }
+
+        // no available streams, look for one to steal from the active list
+        if (newStream == nullptr) {
+            for (auto stream : mActiveStreams) {
+                if (stream->getPriority() <= priority) {
+                    if (newStream == nullptr
+                            || newStream->getPriority() > stream->getPriority()) {
+                        newStream = stream;
+                    }
+                }
+            }
+            if (newStream != nullptr) { // we need to mute as it is still playing.
+                (void)newStream->requestStop(newStream->getStreamID());
+            }
+        }
+
+        // none found, look for a stream that is restarting, evict one.
+        if (newStream == nullptr) {
+            for (auto [unused, stream] : mRestartStreams) {
+                if (stream->getPairPriority() <= priority) {
+                    newStream = stream;
+                    break;
+                }
+            }
+        }
+
+        // DO NOT LOOK into mProcessingStreams as those are held by the StreamManager threads.
+
+        if (newStream == nullptr) {
+            ALOGD("%s: unable to find stream, returning 0", __func__);
+            return 0; // unable to find available stream
+        }
+
+        Stream *pairStream = newStream->getPairStream();
+        streamID = getNextIdForStream(pairStream);
+        pairStream->setPlay(
+                streamID, sound, soundID, leftVolume, rightVolume, priority, loop, rate);
+        if (fromAvailableQueue && kPlayOnCallingThread) {
+            removeFromQueues_l(newStream);
+            mProcessingStreams.emplace(newStream);
+            lock.unlock();
+            if (Stream* nextStream = newStream->playPairStream()) {
+                lock.lock();
+                ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID());
+                addToActiveQueue_l(nextStream);
+            } else {
+                lock.lock();
+                mAvailableStreams.insert(newStream);
+                streamID = 0;
+            }
+            mProcessingStreams.erase(newStream);
+        } else {
+            launchThread = moveToRestartQueue_l(newStream) && needMoreThreads_l();
+        }
+        sanityCheckQueue_l();
+        ALOGV("%s: mStreamManagerLock released", __func__);
+    } // lock
+
+    if (launchThread) {
+        const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+        ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+    }
+    ALOGV("%s: returning %d", __func__, streamID);
+    return streamID;
+}
+
+void StreamManager::moveToRestartQueue(
+        Stream* stream, int32_t activeStreamIDToMatch)
+{
+    ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
+            __func__, stream->getStreamID(), activeStreamIDToMatch);
+    bool restart;
+    {
+        std::lock_guard lock(mStreamManagerLock);
+        sanityCheckQueue_l();
+        if (mProcessingStreams.count(stream) > 0 ||
+                mProcessingStreams.count(stream->getPairStream()) > 0) {
+            ALOGD("%s: attempting to restart processing stream(%d)",
+                    __func__, stream->getStreamID());
+            restart = false;
+        } else {
+            moveToRestartQueue_l(stream, activeStreamIDToMatch);
+            restart = needMoreThreads_l();
+        }
+        sanityCheckQueue_l();
+    }
+    if (restart) {
+        const int32_t id __unused = mThreadPool->launch([this](int32_t id) { run(id); });
+        ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
+    }
+}
+
+bool StreamManager::moveToRestartQueue_l(
+        Stream* stream, int32_t activeStreamIDToMatch)
+{
+    ALOGV("%s(stream(ID)=%d, activeStreamIDToMatch=%d)",
+            __func__, stream->getStreamID(), activeStreamIDToMatch);
+    if (activeStreamIDToMatch > 0 && stream->getStreamID() != activeStreamIDToMatch) {
+        return false;
+    }
+    const ssize_t found = removeFromQueues_l(stream, activeStreamIDToMatch);
+    if (found < 0) return false;
+
+    LOG_ALWAYS_FATAL_IF(found > 1, "stream on %zd > 1 stream lists", found);
+
+    addToRestartQueue_l(stream);
+    mStreamManagerCondition.notify_one();
+    return true;
+}
+
+ssize_t StreamManager::removeFromQueues_l(
+        Stream* stream, int32_t activeStreamIDToMatch) {
+    size_t found = 0;
+    for (auto it = mActiveStreams.begin(); it != mActiveStreams.end(); ++it) {
+        if (*it == stream) {
+            mActiveStreams.erase(it); // we erase the iterator and break (otherwise it not safe).
+            ++found;
+            break;
+        }
+    }
+    // activeStreamIDToMatch is nonzero indicates we proceed only if found.
+    if (found == 0 && activeStreamIDToMatch > 0) {
+        return -1;  // special code: not present on active streams, ignore restart request
+    }
+
+    for (auto it = mRestartStreams.begin(); it != mRestartStreams.end(); ++it) {
+        if (it->second == stream) {
+            mRestartStreams.erase(it);
+            ++found;
+            break;
+        }
+    }
+    found += mAvailableStreams.erase(stream);
+
+    // streams on mProcessingStreams are undergoing processing by the StreamManager thread
+    // and do not participate in normal stream migration.
+    return found;
+}
+
+void StreamManager::addToRestartQueue_l(Stream *stream) {
+    mRestartStreams.emplace(stream->getStopTimeNs(), stream);
+}
+
+void StreamManager::addToActiveQueue_l(Stream *stream) {
+    if (kStealActiveStream_OldestFirst) {
+        mActiveStreams.push_back(stream);  // oldest to newest
+    } else {
+        mActiveStreams.push_front(stream); // newest to oldest
+    }
+}
+
+void StreamManager::run(int32_t id)
+{
+    ALOGV("%s(%d) entering", __func__, id);
+    int64_t waitTimeNs = kWaitTimeBeforeCloseNs;
+    std::unique_lock lock(mStreamManagerLock);
+    while (!mQuit) {
+        mStreamManagerCondition.wait_for(
+                lock, std::chrono::duration<int64_t, std::nano>(waitTimeNs));
+        ALOGV("%s(%d) awake", __func__, id);
+
+        sanityCheckQueue_l();
+
+        if (mQuit || (mRestartStreams.empty() && waitTimeNs == kWaitTimeBeforeCloseNs)) {
+            break;  // end the thread
+        }
+
+        waitTimeNs = kWaitTimeBeforeCloseNs;
+        while (!mQuit && !mRestartStreams.empty()) {
+            const nsecs_t nowNs = systemTime();
+            auto it = mRestartStreams.begin();
+            Stream* const stream = it->second;
+            const int64_t diffNs = stream->getStopTimeNs() - nowNs;
+            if (diffNs > 0) {
+                waitTimeNs = std::min(waitTimeNs, diffNs);
+                break;
+            }
+            mRestartStreams.erase(it);
+            mProcessingStreams.emplace(stream);
+            lock.unlock();
+            stream->stop();
+            ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID());
+            if (Stream* nextStream = stream->playPairStream()) {
+                ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID());
+                lock.lock();
+                if (nextStream->getStopTimeNs() > 0) {
+                    // the next stream was stopped before we can move it to the active queue.
+                    ALOGV("%s(%d) stopping started streamID:%d",
+                            __func__, id, nextStream->getStreamID());
+                    moveToRestartQueue_l(nextStream);
+                } else {
+                    addToActiveQueue_l(nextStream);
+                }
+            } else {
+                lock.lock();
+                mAvailableStreams.insert(stream);
+            }
+            mProcessingStreams.erase(stream);
+            sanityCheckQueue_l();
+        }
+    }
+    ALOGV("%s(%d) exiting", __func__, id);
+}
+
+void StreamManager::dump() const
+{
+    forEach([](const Stream *stream) { stream->dump(); });
+}
+
+void StreamManager::sanityCheckQueue_l() const
+{
+    // We want to preserve the invariant that each stream pair is exactly on one of the queues.
+    const size_t availableStreams = mAvailableStreams.size();
+    const size_t restartStreams = mRestartStreams.size();
+    const size_t activeStreams = mActiveStreams.size();
+    const size_t processingStreams = mProcessingStreams.size();
+    const size_t managedStreams = availableStreams + restartStreams + activeStreams
+                + processingStreams;
+    const size_t totalStreams = getStreamMapSize() >> 1;
+    LOG_ALWAYS_FATAL_IF(managedStreams != totalStreams,
+            "%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
+            "mActiveStreams:%zu + mProcessingStreams:%zu = %zu != total streams %zu",
+            __func__, availableStreams, restartStreams, activeStreams, processingStreams,
+            managedStreams, totalStreams);
+    ALOGV("%s: mAvailableStreams:%zu + mRestartStreams:%zu + "
+            "mActiveStreams:%zu + mProcessingStreams:%zu = %zu (total streams: %zu)",
+            __func__, availableStreams, restartStreams, activeStreams, processingStreams,
+            managedStreams, totalStreams);
+}
+
+} // namespace android::soundpool
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
new file mode 100644
index 0000000..8c98ac9
--- /dev/null
+++ b/media/jni/soundpool/StreamManager.h
@@ -0,0 +1,467 @@
+/*
+ * 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 "Stream.h"
+
+#include <condition_variable>
+#include <future>
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <unordered_set>
+#include <vector>
+
+#include <utils/AndroidThreads.h>
+
+namespace android::soundpool {
+
+// TODO: Move helper classes to a utility file, with separate test.
+
+/**
+ * JavaThread is used like std::thread but for threads that may call the JVM.
+ *
+ * std::thread does not easily attach to the JVM.  We need JVM capable threads
+ * from createThreadEtc() since android binder call optimization may attempt to
+ * call back into Java if the SoundPool runs in system server.
+ *
+ *
+ * No locking is required - the member variables are inherently thread-safe.
+ */
+class JavaThread {
+public:
+    JavaThread(std::function<void()> f, const char *name)
+        : mF{std::move(f)} {
+        createThreadEtc(staticFunction, this, name);
+    }
+
+    JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
+
+    void join() const {
+        mFuture.wait();
+    }
+
+    bool isClosed() const {
+        return mIsClosed;
+    }
+
+private:
+    static int staticFunction(void *data) {
+        JavaThread *jt = static_cast<JavaThread *>(data);
+        jt->mF();
+        jt->mIsClosed = true;
+        jt->mPromise.set_value();
+        return 0;
+    }
+
+    // No locking is provided as these variables are initialized in the constructor
+    // and the members referenced are thread-safe objects.
+    // (mFuture.wait() can block multiple threads.)
+    // Note the order of member variables is reversed for destructor.
+    const std::function<void()> mF;
+    // Used in join() to block until the thread completes.
+    // See https://en.cppreference.com/w/cpp/thread/promise for the void specialization of
+    // promise.
+    std::promise<void>          mPromise;
+    std::future<void>           mFuture{mPromise.get_future()};
+    std::atomic_bool            mIsClosed = false;
+};
+
+/**
+ * The ThreadPool manages thread lifetimes of SoundPool worker threads.
+ *
+ * TODO: the (eventual) goal of ThreadPool is to transparently and cooperatively
+ * maximize CPU utilization while avoiding starvation of other applications.
+ * Some possibilities:
+ *
+ * We should create worker threads when we have SoundPool work and the system is idle.
+ * CPU cycles are "use-it-or-lose-it" when the system is idle.
+ *
+ * We should adjust the priority of worker threads so that the second (and subsequent) worker
+ * threads have lower priority (should we try to promote priority also?).
+ *
+ * We should throttle the spawning of new worker threads, spacing over time, to avoid
+ * creating too many new threads all at once, on initialization.
+ */
+class ThreadPool {
+public:
+    ThreadPool(size_t maxThreadCount, std::string name)
+        : mMaxThreadCount(maxThreadCount)
+        , mName{std::move(name)} { }
+
+    ~ThreadPool() { quit(); }
+
+    size_t getActiveThreadCount() const { return mActiveThreadCount; }
+    size_t getMaxThreadCount() const { return mMaxThreadCount; }
+
+    void quit() {
+        std::list<std::unique_ptr<JavaThread>> threads;
+        {
+            std::lock_guard lock(mThreadLock);
+            if (mQuit) return;  // already joined.
+            mQuit = true;
+            threads = std::move(mThreads);
+            mThreads.clear();
+        }
+        // mQuit set under lock, no more threads will be created.
+        for (auto &thread : threads) {
+            thread->join();
+            thread.reset();
+        }
+        LOG_ALWAYS_FATAL_IF(mActiveThreadCount != 0,
+                "Invalid Active Threads: %zu", (size_t)mActiveThreadCount);
+    }
+
+    // returns a non-zero id if successful, the id is to help logging messages.
+    int32_t launch(std::function<void(int32_t /* id */)> f) {
+        std::list<std::unique_ptr<JavaThread>> threadsToRelease; // release outside of lock.
+        std::lock_guard lock(mThreadLock);
+        if (mQuit) return 0;  // ignore if we have quit
+
+        // clean up threads.
+        for (auto it = mThreads.begin(); it != mThreads.end(); ) {
+            if ((*it)->isClosed()) {
+                threadsToRelease.emplace_back(std::move(*it));
+               it = mThreads.erase(it);
+            } else {
+               ++it;
+            }
+        }
+
+        const size_t threadCount = mThreads.size();
+        if (threadCount < mMaxThreadCount) {
+            // if the id wraps, we don't care about collisions.  it's just for logging.
+            mNextThreadId = mNextThreadId == INT32_MAX ? 1 : ++mNextThreadId;
+            const int32_t id = mNextThreadId;
+            mThreads.emplace_back(std::make_unique<JavaThread>(
+                    [this, id, mf = std::move(f)] { mf(id); --mActiveThreadCount; },
+                    (mName + std::to_string(id)).c_str()));
+            ++mActiveThreadCount;
+            return id;
+        }
+        return 0;
+    }
+
+    // TODO: launch only if load average is low.
+    // This gets the load average
+    // See also std::thread::hardware_concurrency() for the concurrent capability.
+    static double getLoadAvg() {
+        double loadAvg[1];
+        if (getloadavg(loadAvg, std::size(loadAvg)) > 0) {
+            return loadAvg[0];
+        }
+        return -1.;
+    }
+
+private:
+    const size_t            mMaxThreadCount;
+    const std::string       mName;
+
+    std::atomic_size_t      mActiveThreadCount = 0;
+
+    std::mutex              mThreadLock;
+    bool                    mQuit = false;           // GUARDED_BY(mThreadLock)
+    int32_t                 mNextThreadId = 0;       // GUARDED_BY(mThreadLock)
+    std::list<std::unique_ptr<JavaThread>> mThreads; // GUARDED_BY(mThreadLock)
+};
+
+/**
+ * A Perfect HashTable for IDs (key) to pointers (value).
+ *
+ * There are no collisions.  Why? because we generate the IDs for you to look up :-).
+ *
+ * The goal of this hash table is to map an integer ID handle > 0 to a pointer.
+ * We give these IDs in monotonic order (though we may skip if it were to cause a collision).
+ *
+ * The size of the hashtable must be large enough to accommodate the max number of keys.
+ * We suggest 2x.
+ *
+ * Readers are lockless
+ * Single writer could be lockless, but we allow multiple writers through an internal lock.
+ *
+ * For the Key type K, valid keys generated are > 0 (signed or unsigned)
+ * For the Value type V, values are pointers - nullptr means empty.
+ */
+template <typename K, typename V>
+class PerfectHash {
+public:
+    PerfectHash(size_t hashCapacity)
+        : mHashCapacity(hashCapacity)
+        , mK2V{new std::atomic<V>[hashCapacity]()} {
+    }
+
+    // Generate a key for a value V.
+    // There is a testing function getKforV() which checks what the value reports as its key.
+    //
+    // Calls back into getKforV under lock.
+    //
+    // We expect that the hashCapacity is 2x the number of stored keys in order
+    // to have one or two tries to find an empty slot
+    K generateKey(V value, std::function<K(V)> getKforV, K oldKey = 0) {
+        std::lock_guard lock(mHashLock);
+        // try to remove the old key.
+        if (oldKey > 0) {  // key valid
+            const V v = getValue(oldKey);
+            if (v != nullptr) {  // value still valid
+                const K atPosition = getKforV(v);
+                if (atPosition < 0 ||            // invalid value
+                        atPosition == oldKey ||  // value's key still valid and matches old key
+                        ((atPosition ^ oldKey) & (mHashCapacity - 1)) != 0) { // stale key entry
+                    getValue(oldKey) = nullptr;  // invalidate
+                }
+            } // else if value is invalid, no need to invalidate.
+        }
+        // check if we are invalidating only.
+        if (value == nullptr) return 0;
+        // now insert the new value and return the key.
+        size_t tries = 0;
+        for (; tries < mHashCapacity; ++tries) {
+            mNextKey = mNextKey == std::numeric_limits<K>::max() ? 1 : mNextKey + 1;
+            const V v = getValue(mNextKey);
+            //ALOGD("tries: %zu, key:%d value:%p", tries, (int)mNextKey, v);
+            if (v == nullptr) break; // empty
+            const K atPosition = getKforV(v);
+            //ALOGD("tries: %zu  key atPosition:%d", tries, (int)atPosition);
+            if (atPosition < 0 || // invalid value
+                    ((atPosition ^ mNextKey) & (mHashCapacity - 1)) != 0) { // stale key entry
+                break;
+           }
+        }
+        LOG_ALWAYS_FATAL_IF(tries == mHashCapacity, "hash table overflow!");
+        //ALOGD("%s: found after %zu tries", __func__, tries);
+        getValue(mNextKey) = value;
+        return mNextKey;
+    }
+
+    std::atomic<V> &getValue(K key) { return mK2V[key & (mHashCapacity - 1)]; }
+    const std::atomic_int32_t &getValue(K key) const { return mK2V[key & (mHashCapacity - 1)]; }
+
+private:
+    mutable std::mutex          mHashLock;
+    const size_t                mHashCapacity; // size of mK2V no lock needed.
+    std::unique_ptr<std::atomic<V>[]> mK2V;    // no lock needed for read access.
+    K                           mNextKey{};    // GUARDED_BY(mHashLock)
+};
+
+/**
+ * StreamMap contains the all the valid streams available to SoundPool.
+ *
+ * There is no Lock required for this class because the streams are
+ * allocated in the constructor, the lookup is lockless, and the Streams
+ * returned are locked internally.
+ *
+ * The lookup uses a perfect hash.
+ * It is possible to use a lockless hash table or to use a stripe-locked concurrent
+ * hashmap for essentially lock-free lookup.
+ *
+ * This follows Map-Reduce parallelism model.
+ * https://en.wikipedia.org/wiki/MapReduce
+ *
+ * Conceivably the forEach could be parallelized using std::for_each with a
+ * std::execution::par policy.
+ *
+ * https://en.cppreference.com/w/cpp/algorithm/for_each
+ */
+class StreamMap {
+public:
+    explicit StreamMap(int32_t streams);
+
+    // Returns the stream associated with streamID or nullptr if not found.
+    // This need not be locked.
+    // The stream ID will never migrate to another Stream, but it may change
+    // underneath you.  The Stream operations that take a streamID will confirm
+    // that the streamID matches under the Stream lock before executing otherwise
+    // it ignores the command as stale.
+    Stream* findStream(int32_t streamID) const;
+
+    // Iterates through the stream pool applying the function f.
+    // Since this enumerates over every single stream, it is unlocked.
+    //
+    // See related: https://en.cppreference.com/w/cpp/algorithm/for_each
+    void forEach(std::function<void(const Stream *)>f) const {
+        for (size_t i = 0; i < mStreamPoolSize; ++i) {
+            f(&mStreamPool[i]);
+        }
+    }
+
+    void forEach(std::function<void(Stream *)>f) {
+        for (size_t i = 0; i < mStreamPoolSize; ++i) {
+            f(&mStreamPool[i]);
+        }
+    }
+
+    // Returns the pair stream for a given Stream.
+    // This need not be locked as it is a property of the pointer address.
+    Stream* getPairStream(const Stream* stream) const {
+        const size_t index = streamPosition(stream);
+        return &mStreamPool[index ^ 1];
+    }
+
+    // find the position of the stream in mStreamPool array.
+    size_t streamPosition(const Stream* stream) const; // no lock needed
+
+    size_t getStreamMapSize() const {
+        return mStreamPoolSize;
+    }
+
+    // find the next valid ID for a stream and store in hash table.
+    int32_t getNextIdForStream(Stream* stream) const;
+
+private:
+
+    // use the hash table to attempt to find the stream.
+    // nullptr is returned if the lookup fails.
+    Stream* lookupStreamFromId(int32_t streamID) const;
+
+    // The stream pool is initialized in the constructor, effectively const.
+    // no locking required for access.
+    //
+    // The constructor parameter "streams" results in streams pairs of streams.
+    // We have twice as many streams because we wish to return a streamID "handle"
+    // back to the app immediately, while we may be stopping the other stream in the
+    // pair to get its AudioTrack :-).
+    //
+    // Of the stream pair, only one of the streams may have an AudioTrack.
+    // The fixed association of a stream pair allows callbacks from the AudioTrack
+    // to be associated properly to either one or the other of the stream pair.
+    //
+    // TODO: The stream pair arrangement can be removed if we have better AudioTrack
+    // callback handling (being able to remove and change the callback after construction).
+    //
+    // Streams may be accessed anytime off of the stream pool
+    // as there is internal locking on each stream.
+    std::unique_ptr<Stream[]>   mStreamPool;        // no lock needed for access.
+    size_t                      mStreamPoolSize;    // no lock needed for access.
+
+    // In order to find the Stream from a StreamID, we could do a linear lookup in mStreamPool.
+    // As an alternative, one could use stripe-locked or lock-free concurrent hashtables.
+    //
+    // When considering linear search vs hashmap, verify the typical use-case size.
+    // Linear search is faster than std::unordered_map (circa 2018) for less than 40 elements.
+    // [ Skarupke, M. (2018), "You Can Do Better than std::unordered_map: New and Recent
+    // Improvements to Hash Table Performance." C++Now 2018. cppnow.org, see
+    // https://www.youtube.com/watch?v=M2fKMP47slQ ]
+    //
+    // Here, we use a PerfectHash of Id to Stream *, since we can control the
+    // StreamID returned to the user.  This allows O(1) read access to mStreamPool lock-free.
+    //
+    // We prefer that the next stream ID is monotonic for aesthetic reasons
+    // (if we didn't care about monotonicity, a simple method is to apply a generation count
+    // to each stream in the unused upper bits of its index in mStreamPool for the id).
+    //
+    std::unique_ptr<PerfectHash<int32_t, Stream *>> mPerfectHash;
+};
+
+/**
+ * StreamManager is used to manage the streams (accessed by StreamID from Java).
+ *
+ * Locking order (proceeds from application to component).
+ *  SoundPool mApiLock (if needed) -> StreamManager mStreamManagerLock
+ *                                 -> pair Stream mLock -> queued Stream mLock
+ */
+class StreamManager : public StreamMap {
+public:
+    // Note: the SoundPool pointer is only used for stream initialization.
+    // It is not stored in StreamManager.
+    StreamManager(int32_t streams, size_t threads, const audio_attributes_t* attributes);
+    ~StreamManager();
+
+    // Returns positive streamID on success, 0 on failure.  This is locked.
+    int32_t queueForPlay(const std::shared_ptr<Sound> &sound,
+            int32_t soundID, float leftVolume, float rightVolume,
+            int32_t priority, int32_t loop, float rate);
+
+    ///////////////////////////////////////////////////////////////////////
+    // Called from soundpool::Stream
+
+    const audio_attributes_t* getAttributes() const { return &mAttributes; }
+
+    // Moves the stream to the restart queue (called upon BUFFER_END of the static track)
+    // this is locked internally.
+    // If activeStreamIDToMatch is nonzero, it will only move to the restart queue
+    // if the streamIDToMatch is found on the active queue.
+    void moveToRestartQueue(Stream* stream, int32_t activeStreamIDToMatch = 0);
+
+private:
+
+    void run(int32_t id);                        // worker thread, takes lock internally.
+    void dump() const;                           // no lock needed
+
+    // returns true if more worker threads are needed.
+    bool needMoreThreads_l() {
+        return mRestartStreams.size() > 0 &&
+                (mThreadPool->getActiveThreadCount() == 0
+                || std::distance(mRestartStreams.begin(),
+                        mRestartStreams.upper_bound(systemTime()))
+                        > (ptrdiff_t)mThreadPool->getActiveThreadCount());
+    }
+
+    // returns true if the stream was added.
+    bool moveToRestartQueue_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
+    // returns number of queues the stream was removed from (should be 0 or 1);
+    // a special code of -1 is returned if activeStreamIDToMatch is > 0 and
+    // the stream wasn't found on the active queue.
+    ssize_t removeFromQueues_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
+    void addToRestartQueue_l(Stream *stream);
+    void addToActiveQueue_l(Stream *stream);
+    void sanityCheckQueue_l() const;
+
+    const audio_attributes_t mAttributes;
+    std::unique_ptr<ThreadPool> mThreadPool;                  // locked internally
+
+    // mStreamManagerLock is used to lock access for transitions between the
+    // 4 stream queues by the Manager Thread or by the user initiated play().
+    // A stream pair has exactly one stream on exactly one of the queues.
+    std::mutex                  mStreamManagerLock;
+    std::condition_variable     mStreamManagerCondition;
+
+    bool                        mQuit = false;      // GUARDED_BY(mStreamManagerLock)
+
+    // There are constructor arg "streams" pairs of streams, only one of each
+    // pair on the 4 stream queues below.  The other stream in the pair serves as
+    // placeholder to accumulate user changes, pending actual availability of the
+    // AudioTrack, as it may be in use, requiring stop-then-restart.
+    //
+    // The 4 queues are implemented in the appropriate STL container based on perceived
+    // optimality.
+
+    // 1) mRestartStreams: Streams awaiting stop.
+    // The paired stream may be active (but with no AudioTrack), and will be restarted
+    // with an active AudioTrack when the current stream is stopped.
+    std::multimap<int64_t /* stopTimeNs */, Stream*>
+                                mRestartStreams;    // GUARDED_BY(mStreamManagerLock)
+
+    // 2) mActiveStreams: Streams that are active.
+    // The paired stream will be inactive.
+    // This is in order of specified by kStealActiveStream_OldestFirst
+    std::list<Stream*>          mActiveStreams;     // GUARDED_BY(mStreamManagerLock)
+
+    // 3) mAvailableStreams: Streams that are inactive.
+    // The paired stream will also be inactive.
+    // No particular order.
+    std::unordered_set<Stream*> mAvailableStreams;  // GUARDED_BY(mStreamManagerLock)
+
+    // 4) mProcessingStreams: Streams that are being processed by the ManagerThreads
+    // When on this queue, the stream and its pair are not available for stealing.
+    // Each ManagerThread will have at most one stream on the mProcessingStreams queue.
+    // The paired stream may be active or restarting.
+    // No particular order.
+    std::unordered_set<Stream*> mProcessingStreams; // GUARDED_BY(mStreamManagerLock)
+};
+
+} // namespace android::soundpool
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
index a0d2050..2da45b6 100644
--- a/media/native/midi/Android.bp
+++ b/media/native/midi/Android.bp
@@ -17,6 +17,7 @@
 
     srcs: [
         "amidi.cpp",
+        "MidiDeviceInfo.cpp",
         ":IMidiDeviceServer.aidl",
     ],
 
@@ -31,12 +32,14 @@
         "-fvisibility=hidden",
     ],
 
+    header_libs: [
+        "media_ndk_headers",
+    ],
+
     shared_libs: [
         "liblog",
         "libbinder",
         "libutils",
-        "libmedia",
-        "libmediandk",
         "libandroid_runtime",
     ],
 
diff --git a/media/native/midi/MidiDeviceInfo.cpp b/media/native/midi/MidiDeviceInfo.cpp
new file mode 100644
index 0000000..ac68d26
--- /dev/null
+++ b/media/native/midi/MidiDeviceInfo.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "MidiDeviceInfo"
+
+#include <MidiDeviceInfo.h>
+
+#include <binder/Parcel.h>
+#include <log/log.h>
+#include <utils/Errors.h>
+#include <utils/String16.h>
+
+namespace android {
+namespace media {
+namespace midi {
+
+// The constant values need to be kept in sync with MidiDeviceInfo.java.
+// static
+const char* const MidiDeviceInfo::PROPERTY_NAME = "name";
+const char* const MidiDeviceInfo::PROPERTY_MANUFACTURER = "manufacturer";
+const char* const MidiDeviceInfo::PROPERTY_PRODUCT = "product";
+const char* const MidiDeviceInfo::PROPERTY_VERSION = "version";
+const char* const MidiDeviceInfo::PROPERTY_SERIAL_NUMBER = "serial_number";
+const char* const MidiDeviceInfo::PROPERTY_ALSA_CARD = "alsa_card";
+const char* const MidiDeviceInfo::PROPERTY_ALSA_DEVICE = "alsa_device";
+
+String16 MidiDeviceInfo::getProperty(const char* propertyName) {
+    String16 value;
+    if (mProperties.getString(String16(propertyName), &value)) {
+        return value;
+    } else {
+        return String16();
+    }
+}
+
+#define RETURN_IF_FAILED(calledOnce)                                     \
+    {                                                                    \
+        status_t returnStatus = calledOnce;                              \
+        if (returnStatus) {                                              \
+            ALOGE("Failed at %s:%d (%s)", __FILE__, __LINE__, __func__); \
+            return returnStatus;                                         \
+         }                                                               \
+    }
+
+status_t MidiDeviceInfo::writeToParcel(Parcel* parcel) const {
+    // Needs to be kept in sync with code in MidiDeviceInfo.java
+    RETURN_IF_FAILED(parcel->writeInt32(mType));
+    RETURN_IF_FAILED(parcel->writeInt32(mId));
+    RETURN_IF_FAILED(parcel->writeInt32((int32_t)mInputPortNames.size()));
+    RETURN_IF_FAILED(parcel->writeInt32((int32_t)mOutputPortNames.size()));
+    RETURN_IF_FAILED(writeStringVector(parcel, mInputPortNames));
+    RETURN_IF_FAILED(writeStringVector(parcel, mOutputPortNames));
+    RETURN_IF_FAILED(parcel->writeInt32(mIsPrivate ? 1 : 0));
+    RETURN_IF_FAILED(mProperties.writeToParcel(parcel));
+    // This corresponds to "extra" properties written by Java code
+    RETURN_IF_FAILED(mProperties.writeToParcel(parcel));
+    return OK;
+}
+
+status_t MidiDeviceInfo::readFromParcel(const Parcel* parcel) {
+    // Needs to be kept in sync with code in MidiDeviceInfo.java
+    RETURN_IF_FAILED(parcel->readInt32(&mType));
+    RETURN_IF_FAILED(parcel->readInt32(&mId));
+    int32_t inputPortCount;
+    RETURN_IF_FAILED(parcel->readInt32(&inputPortCount));
+    int32_t outputPortCount;
+    RETURN_IF_FAILED(parcel->readInt32(&outputPortCount));
+    RETURN_IF_FAILED(readStringVector(parcel, &mInputPortNames, inputPortCount));
+    RETURN_IF_FAILED(readStringVector(parcel, &mOutputPortNames, outputPortCount));
+    int32_t isPrivate;
+    RETURN_IF_FAILED(parcel->readInt32(&isPrivate));
+    mIsPrivate = isPrivate == 1;
+    RETURN_IF_FAILED(mProperties.readFromParcel(parcel));
+    // Ignore "extra" properties as they may contain Java Parcelables
+    return OK;
+}
+
+status_t MidiDeviceInfo::readStringVector(
+        const Parcel* parcel, Vector<String16> *vectorPtr, size_t defaultLength) {
+    std::unique_ptr<std::vector<std::unique_ptr<String16>>> v;
+    status_t result = parcel->readString16Vector(&v);
+    if (result != OK) return result;
+    vectorPtr->clear();
+    if (v.get() != nullptr) {
+        for (const auto& iter : *v) {
+            if (iter.get() != nullptr) {
+                vectorPtr->push_back(*iter);
+            } else {
+                vectorPtr->push_back(String16());
+            }
+        }
+    } else {
+        vectorPtr->resize(defaultLength);
+    }
+    return OK;
+}
+
+status_t MidiDeviceInfo::writeStringVector(Parcel* parcel, const Vector<String16>& vector) const {
+    std::vector<String16> v;
+    for (size_t i = 0; i < vector.size(); ++i) {
+        v.push_back(vector[i]);
+    }
+    return parcel->writeString16Vector(v);
+}
+
+// Vector does not define operator==
+static inline bool areVectorsEqual(const Vector<String16>& lhs, const Vector<String16>& rhs) {
+    if (lhs.size() != rhs.size()) return false;
+    for (size_t i = 0; i < lhs.size(); ++i) {
+        if (lhs[i] != rhs[i]) return false;
+    }
+    return true;
+}
+
+bool operator==(const MidiDeviceInfo& lhs, const MidiDeviceInfo& rhs) {
+    return (lhs.mType == rhs.mType && lhs.mId == rhs.mId &&
+            areVectorsEqual(lhs.mInputPortNames, rhs.mInputPortNames) &&
+            areVectorsEqual(lhs.mOutputPortNames, rhs.mOutputPortNames) &&
+            lhs.mProperties == rhs.mProperties &&
+            lhs.mIsPrivate == rhs.mIsPrivate);
+}
+
+}  // namespace midi
+}  // namespace media
+}  // namespace android
diff --git a/media/native/midi/MidiDeviceInfo.h b/media/native/midi/MidiDeviceInfo.h
new file mode 100644
index 0000000..5b4a241
--- /dev/null
+++ b/media/native/midi/MidiDeviceInfo.h
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_DEVICE_INFO_H
+#define ANDROID_MEDIA_MIDI_DEVICE_INFO_H
+
+#include <binder/Parcelable.h>
+#include <binder/PersistableBundle.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+namespace android {
+namespace media {
+namespace midi {
+
+class MidiDeviceInfo : public Parcelable {
+public:
+    MidiDeviceInfo() = default;
+    virtual ~MidiDeviceInfo() = default;
+    MidiDeviceInfo(const MidiDeviceInfo& midiDeviceInfo) = default;
+
+    status_t writeToParcel(Parcel* parcel) const override;
+    status_t readFromParcel(const Parcel* parcel) override;
+
+    int getType() const { return mType; }
+    int getUid() const { return mId; }
+    bool isPrivate() const { return mIsPrivate; }
+    const Vector<String16>& getInputPortNames() const { return mInputPortNames; }
+    const Vector<String16>&  getOutputPortNames() const { return mOutputPortNames; }
+    String16 getProperty(const char* propertyName);
+
+    // The constants need to be kept in sync with MidiDeviceInfo.java
+    enum {
+        TYPE_USB = 1,
+        TYPE_VIRTUAL = 2,
+        TYPE_BLUETOOTH = 3,
+    };
+    static const char* const PROPERTY_NAME;
+    static const char* const PROPERTY_MANUFACTURER;
+    static const char* const PROPERTY_PRODUCT;
+    static const char* const PROPERTY_VERSION;
+    static const char* const PROPERTY_SERIAL_NUMBER;
+    static const char* const PROPERTY_ALSA_CARD;
+    static const char* const PROPERTY_ALSA_DEVICE;
+
+    friend bool operator==(const MidiDeviceInfo& lhs, const MidiDeviceInfo& rhs);
+    friend bool operator!=(const MidiDeviceInfo& lhs, const MidiDeviceInfo& rhs) {
+        return !(lhs == rhs);
+    }
+
+private:
+    status_t readStringVector(
+            const Parcel* parcel, Vector<String16> *vectorPtr, size_t defaultLength);
+    status_t writeStringVector(Parcel* parcel, const Vector<String16>& vector) const;
+
+    int32_t mType;
+    int32_t mId;
+    Vector<String16> mInputPortNames;
+    Vector<String16> mOutputPortNames;
+    os::PersistableBundle mProperties;
+    bool mIsPrivate;
+};
+
+}  // namespace midi
+}  // namespace media
+}  // namespace android
+
+#endif  // ANDROID_MEDIA_MIDI_DEVICE_INFO_H
diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp
index 46f2815..35c4d42 100644
--- a/media/native/midi/amidi.cpp
+++ b/media/native/midi/amidi.cpp
@@ -26,7 +26,7 @@
 #include <core_jni_helpers.h>
 
 #include "android/media/midi/BpMidiDeviceServer.h"
-#include "media/MidiDeviceInfo.h"
+#include "MidiDeviceInfo.h"
 
 #include "include/amidi/AMidi.h"
 #include "amidi_internal.h"
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 7c1af4a..91297b0 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -110,3 +110,36 @@
     symbol_file: "libandroid_net.map.txt",
     unversioned: true,
 }
+
+
+// Aidl library for platform compat.
+cc_library_shared {
+    name: "lib-platform-compat-native-api",
+    cflags: [
+            "-Wall",
+            "-Werror",
+            "-Wno-missing-field-initializers",
+            "-Wno-unused-variable",
+            "-Wunused-parameter",
+        ],
+    shared_libs: [
+        "libbinder",
+         "libutils",
+    ],
+    aidl: {
+        local_include_dirs: ["aidl"],
+        export_aidl_headers: true,
+    },
+    srcs: [
+         ":platform-compat-native-aidl",
+    ],
+    export_include_dirs: ["aidl"],
+}
+
+filegroup {
+    name: "platform-compat-native-aidl",
+    srcs: [
+        "aidl/com/android/internal/compat/IPlatformCompatNative.aidl",
+    ],
+    path: "aidl",
+}
\ No newline at end of file
diff --git a/native/android/aidl/com/android/internal/compat/IPlatformCompatNative.aidl b/native/android/aidl/com/android/internal/compat/IPlatformCompatNative.aidl
new file mode 100644
index 0000000..347e4e8
--- /dev/null
+++ b/native/android/aidl/com/android/internal/compat/IPlatformCompatNative.aidl
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+/**
+ * Platform native private API for talking with the PlatformCompat service.
+ *
+ * <p> Should be used for gating and logging from non-app processes running cpp code.
+ * For app processes please use android.compat.Compatibility API.
+ *
+ * {@hide}
+ */
+interface IPlatformCompatNative
+{
+    /**
+     * Reports that a compatibility change is affecting an app process now.
+     *
+     * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, String)},
+     * you do not need to call this API directly. The change will be reported for you.
+     *
+     * @param changeId    The ID of the compatibility change taking effect.
+     * @param userId      The ID of the user that the operation is done for.
+     * @param packageName The package name of the app in question.
+     */
+     void reportChangeByPackageName(long changeId, @utf8InCpp String packageName, int userId);
+
+    /**
+     * Reports that a compatibility change is affecting an app process now.
+     *
+     * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, int)},
+     * you do not need to call this API directly. The change will be reported for you.
+     *
+     * @param changeId The ID of the compatibility change taking effect.
+     * @param uid      The UID of the app in question.
+     */
+    void reportChangeByUid(long changeId, int uid);
+
+    /**
+     * Query if a given compatibility change is enabled for an app process. This method should
+     * be called when implementing functionality on behalf of the affected app.
+     *
+     * <p>Returns {@code true} if there is no installed package by the provided package name.
+     *
+     * <p>If this method returns {@code true}, the calling code should implement the compatibility
+     * change, resulting in differing behaviour compared to earlier releases. If this method
+     * returns
+     * {@code false}, the calling code should behave as it did in earlier releases.
+     *
+     * <p>It will also report the change as {@link #reportChange(long, String)} would, so there is
+     * no need to call that method directly.
+     *
+     * @param changeId    The ID of the compatibility change in question.
+     * @param packageName The package name of the app in question.
+     * @param userId      The ID of the user that the operation is done for.
+     * @return {@code true} if the change is enabled for the current app.
+     */
+    boolean isChangeEnabledByPackageName(long changeId, @utf8InCpp String packageName, int userId);
+
+    /**
+     * Query if a given compatibility change is enabled for an app process. This method should
+     * be called when implementing functionality on behalf of the affected app.
+     *
+     * <p> Returns {@code true} if there are no installed packages for the required UID, or if the
+     * change is enabled for ALL of the installed packages associated with the provided UID. Please
+     * use a more specific API if you want a different behaviour for multi-package UIDs.
+     *
+     * <p>If this method returns {@code true}, the calling code should implement the compatibility
+     * change, resulting in differing behaviour compared to earlier releases. If this method
+     * returns {@code false}, the calling code should behave as it did in earlier releases.
+     *
+     * <p>It will also report the change as {@link #reportChange(long, int)} would, so there is
+     * no need to call that method directly.
+     *
+     * @param changeId The ID of the compatibility change in question.
+     * @param uid      The UID of the app in question.
+     * @return {@code true} if the change is enabled for the current app.
+     */
+    boolean isChangeEnabledByUid(long changeId, int uid);
+}
\ No newline at end of file
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 53c0122..b34b31a 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -294,7 +294,7 @@
 
         auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
 
-        for (const auto& [surfaceControl, acquireTime, previousReleaseFence] : surfaceControlStats) {
+        for (const auto& [surfaceControl, acquireTime, previousReleaseFence, transformHint] : surfaceControlStats) {
             ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
             aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
             aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
index 011d5ea..be4b889 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -20,6 +20,7 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.navigationbar.car.CarFacetButtonController;
 import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index b1067f8..93e553f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -23,6 +23,7 @@
 
 import com.android.systemui.car.CarNotificationEntryManager;
 import com.android.systemui.car.CarNotificationInterruptionStateProvider;
+import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.power.EnhancedEstimates;
@@ -101,6 +102,9 @@
             CarSystemUIRootComponent systemUIRootComponent);
 
     @Binds
+    public abstract StatusBar bindStatusBar(CarStatusBar statusBar);
+
+    @Binds
     @IntoMap
     @ClassKey(StatusBar.class)
     public abstract SystemUI providesStatusBar(CarStatusBar statusBar);
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
index 325c988..c2847c8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
@@ -16,6 +16,12 @@
 
 package com.android.systemui;
 
+import com.android.systemui.dagger.DependencyBinder;
+import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.SystemServicesModule;
+import com.android.systemui.dagger.SystemUIModule;
+import com.android.systemui.dagger.SystemUIRootComponent;
+
 import javax.inject.Singleton;
 
 import dagger.Component;
@@ -26,6 +32,7 @@
                 DependencyProvider.class,
                 DependencyBinder.class,
                 SystemUIFactory.ContextHolder.class,
+                SystemServicesModule.class,
                 SystemUIModule.class,
                 CarSystemUIModule.class,
                 CarSystemUIBinder.class
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 5bb3530..6fba1d5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.navigationbar.car;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.content.Context;
 import android.graphics.PixelFormat;
 import android.inputmethodservice.InputMethodService;
@@ -35,6 +33,7 @@
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NavigationBarController;
@@ -47,7 +46,6 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 import dagger.Lazy;
 
@@ -96,7 +94,7 @@
             WindowManager windowManager,
             DeviceProvisionedController deviceProvisionedController,
             Lazy<FacetButtonTaskStackListener> facetButtonTaskStackListener,
-            @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+            @MainHandler Handler mainHandler,
             Lazy<KeyguardStateController> keyguardStateController,
             Lazy<CarFacetButtonController> facetButtonController,
             Lazy<NavigationBarController> navigationBarController,
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/NavigationBarViewFactory.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/NavigationBarViewFactory.java
index 128721e..519b33a2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/NavigationBarViewFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/NavigationBarViewFactory.java
@@ -108,7 +108,7 @@
 
     private ViewGroup getWindowCached(Type type) {
         if (mCachedContainerMap.containsKey(type)) {
-            return mCachedViewMap.get(type);
+            return mCachedContainerMap.get(type);
         }
 
         ViewGroup window = (ViewGroup) View.inflate(mContext,
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3424eea..16073ae 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -106,6 +106,7 @@
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
@@ -161,9 +162,18 @@
     private Drawable mNotificationPanelBackground;
 
     private ViewGroup mTopNavigationBarContainer;
+    private ViewGroup mNavigationBarWindow;
+    private ViewGroup mLeftNavigationBarWindow;
+    private ViewGroup mRightNavigationBarWindow;
     private CarNavigationBarView mTopNavigationBarView;
+    private CarNavigationBarView mNavigationBarView;
+    private CarNavigationBarView mLeftNavigationBarView;
+    private CarNavigationBarView mRightNavigationBarView;
 
     private final Object mQueueLock = new Object();
+    private boolean mShowLeft;
+    private boolean mShowRight;
+    private boolean mShowBottom;
     private final NavigationBarViewFactory mNavigationBarViewFactory;
     private CarFacetButtonController mCarFacetButtonController;
     private DeviceProvisionedController mDeviceProvisionedController;
@@ -290,6 +300,7 @@
             DozeParameters dozeParameters,
             ScrimController scrimController,
             Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
+            Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
 
             /* Car Settings injected components. */
             NavigationBarViewFactory navigationBarViewFactory) {
@@ -352,7 +363,8 @@
                 notifLog,
                 dozeParameters,
                 scrimController,
-                lockscreenWallpaperLazy);
+                lockscreenWallpaperLazy,
+                biometricUnlockControllerLazy);
         mScrimController = scrimController;
         mNavigationBarViewFactory = navigationBarViewFactory;
     }
@@ -429,6 +441,16 @@
         mHvacController.removeAllComponents();
         mCarFacetButtonController.removeAll();
 
+        if (mNavigationBarWindow != null) {
+            mNavigationBarView = null;
+        }
+        if (mLeftNavigationBarWindow != null) {
+            mLeftNavigationBarView = null;
+        }
+        if (mRightNavigationBarWindow != null) {
+            mRightNavigationBarView = null;
+        }
+
         buildNavBarContent();
         // CarFacetButtonController was reset therefore we need to re-add the status bar elements
         // to the controller.
@@ -451,28 +473,28 @@
      * the full screen user selector is shown.
      */
     void setNavBarVisibility(@View.Visibility int visibility) {
-        if (mNavigationBarViewFactory.getBottomWindow() != null) {
-            mNavigationBarViewFactory.getBottomWindow().setVisibility(visibility);
+        if (mNavigationBarWindow != null) {
+            mNavigationBarWindow.setVisibility(visibility);
         }
-        if (mNavigationBarViewFactory.getLeftWindow() != null) {
-            mNavigationBarViewFactory.getLeftWindow().setVisibility(visibility);
+        if (mLeftNavigationBarWindow != null) {
+            mLeftNavigationBarWindow.setVisibility(visibility);
         }
-        if (mNavigationBarViewFactory.getRightWindow() != null) {
-            mNavigationBarViewFactory.getRightWindow().setVisibility(visibility);
+        if (mRightNavigationBarWindow != null) {
+            mRightNavigationBarWindow.setVisibility(visibility);
         }
     }
 
     @Override
     public boolean hideKeyguard() {
         boolean result = super.hideKeyguard();
-        if (mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser) != null) {
-            mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser).hideKeyguardButtons();
+        if (mNavigationBarView != null) {
+            mNavigationBarView.hideKeyguardButtons();
         }
-        if (mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser) != null) {
-            mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser).hideKeyguardButtons();
+        if (mLeftNavigationBarView != null) {
+            mLeftNavigationBarView.hideKeyguardButtons();
         }
-        if (mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser) != null) {
-            mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser).hideKeyguardButtons();
+        if (mRightNavigationBarView != null) {
+            mRightNavigationBarView.hideKeyguardButtons();
         }
         return result;
     }
@@ -487,14 +509,14 @@
      * Switch to the keyguard applicable content contained in the nav bars
      */
     private void updateNavBarForKeyguardContent() {
-        if (mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser) != null) {
-            mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser).showKeyguardButtons();
+        if (mNavigationBarView != null) {
+            mNavigationBarView.showKeyguardButtons();
         }
-        if (mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser) != null) {
-            mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser).showKeyguardButtons();
+        if (mLeftNavigationBarView != null) {
+            mLeftNavigationBarView.showKeyguardButtons();
         }
-        if (mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser) != null) {
-            mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser).showKeyguardButtons();
+        if (mRightNavigationBarView != null) {
+            mRightNavigationBarView.showKeyguardButtons();
         }
     }
 
@@ -599,26 +621,19 @@
         mNotificationDataManager = new NotificationDataManager();
         mNotificationDataManager.setOnUnseenCountUpdateListener(
                 () -> {
-                    if (mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser) != null
-                            && mNotificationDataManager != null) {
+                    if (mNavigationBarView != null && mNotificationDataManager != null) {
                         Boolean hasUnseen =
                                 mNotificationDataManager.getUnseenNotificationCount() > 0;
-                        if (mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser) != null) {
-                            mNavigationBarViewFactory.getBottomBar(
-                                    mDeviceIsSetUpForUser).toggleNotificationUnseenIndicator(
-                                    hasUnseen);
+                        if (mNavigationBarView != null) {
+                            mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
                         }
 
-                        if (mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser) != null) {
-                            mNavigationBarViewFactory.getLeftBar(
-                                    mDeviceIsSetUpForUser).toggleNotificationUnseenIndicator(
-                                    hasUnseen);
+                        if (mLeftNavigationBarView != null) {
+                            mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
                         }
 
-                        if (mNavigationBarViewFactory.getRightBar(mDeviceIsSetUpForUser) != null) {
-                            mNavigationBarViewFactory.getRightBar(
-                                    mDeviceIsSetUpForUser).toggleNotificationUnseenIndicator(
-                                    hasUnseen);
+                        if (mRightNavigationBarView != null) {
+                            mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
                         }
                     }
                 });
@@ -873,6 +888,10 @@
 
     @Override
     protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
+        mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
+        mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar);
+        mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar);
+
         buildNavBarWindows();
         buildNavBarContent();
     }
@@ -880,22 +899,40 @@
     private void buildNavBarContent() {
         buildTopBar();
 
-        CarNavigationBarView bottom = mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser);
-        bottom.setStatusBar(this);
-        bottom.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
+        if (mShowBottom) {
+            mNavigationBarView = mNavigationBarViewFactory.getBottomBar(mDeviceIsSetUpForUser);
+            mNavigationBarView.setStatusBar(this);
+            mNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
+        }
 
-        CarNavigationBarView left = mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser);
-        left.setStatusBar(this);
-        left.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
+        if (mShowLeft) {
+            mLeftNavigationBarView = mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser);
+            mLeftNavigationBarView.setStatusBar(this);
+            mLeftNavigationBarView.setStatusBarWindowTouchListener(
+                    mNavBarNotificationTouchListener);
+        }
 
-        CarNavigationBarView right = mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser);
-        right.setStatusBar(this);
-        right.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
+        if (mShowRight) {
+            mRightNavigationBarView = mNavigationBarViewFactory.getLeftBar(mDeviceIsSetUpForUser);
+            mRightNavigationBarView.setStatusBar(this);
+            mRightNavigationBarView.setStatusBarWindowTouchListener(
+                    mNavBarNotificationTouchListener);
+        }
     }
 
     private void buildNavBarWindows() {
         mTopNavigationBarContainer = mStatusBarWindow
                 .findViewById(R.id.car_top_navigation_bar_container);
+
+        if (mShowBottom) {
+            mNavigationBarWindow = mNavigationBarViewFactory.getBottomWindow();
+        }
+        if (mShowLeft) {
+            mLeftNavigationBarWindow = mNavigationBarViewFactory.getLeftWindow();
+        }
+        if (mShowRight) {
+            mRightNavigationBarWindow = mNavigationBarViewFactory.getRightWindow();
+        }
     }
 
     private void buildTopBar() {
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 beea1ac..3014452 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -55,7 +55,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * Displays a GridLayout with icons for the users in the system to allow switching between users.
@@ -109,21 +108,16 @@
      * @return the adapter
      */
     public void buildAdapter() {
-        List<UserRecord> userRecords = createUserRecords(getAllUsers());
+        List<UserRecord> userRecords = createUserRecords(getUsersForUserGrid());
         mAdapter = new UserAdapter(mContext, userRecords);
         super.setAdapter(mAdapter);
     }
 
-    private List<UserInfo> getAllUsers() {
-        Stream<UserInfo> userListStream =
-                mUserManager.getUsers(/* excludeDying= */ true).stream();
-
-        if (UserManager.isHeadlessSystemUserMode()) {
-            userListStream =
-                    userListStream.filter(userInfo -> userInfo.id != UserHandle.USER_SYSTEM);
-        }
-        userListStream = userListStream.filter(userInfo -> userInfo.supportsSwitchToByUser());
-        return userListStream.collect(Collectors.toList());
+    private List<UserInfo> getUsersForUserGrid() {
+        return mUserManager.getUsers(/* excludeDying= */ true)
+                .stream()
+                .filter(UserInfo::supportsSwitchToByUser)
+                .collect(Collectors.toList());
     }
 
     private List<UserRecord> createUserRecords(List<UserInfo> userInfoList) {
@@ -189,7 +183,7 @@
 
     private void onUsersUpdate() {
         mAdapter.clearUsers();
-        mAdapter.updateUsers(createUserRecords(getAllUsers()));
+        mAdapter.updateUsers(createUserRecords(getUsersForUserGrid()));
         mAdapter.notifyDataSetChanged();
     }
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index d0a63f0..22c7c7a 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -143,6 +143,7 @@
     private boolean mHovering;
     private int mCurrentlyDisplayingGroupId;
     private boolean mShowing;
+    private boolean mDismissing;
     private boolean mExpanded;
     private View mExpandIcon;
     private final ServiceConnection mServiceConnection = new ServiceConnection() {
@@ -244,6 +245,7 @@
 
         mHovering = false;
         mShowing = false;
+        mDismissing = false;
         mExpanded = false;
         mWindow = mDialog.getWindow();
         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
@@ -335,14 +337,11 @@
 
         mHandler.removeMessages(H.DISMISS);
         mHandler.removeMessages(H.SHOW);
-        if (!mShowing) {
+        if (!mShowing || mDismissing) {
             return;
         }
 
-        mListView.animate().cancel();
-
-        mListView.setTranslationY(0);
-        mListView.setAlpha(1);
+        mDismissing = true;
         mListView.animate()
                 .alpha(0)
                 .translationY(-mListView.getHeight())
@@ -354,7 +353,7 @@
                     }
                     mDialog.dismiss();
                     mShowing = false;
-                    mShowing = false;
+                    mDismissing = false;
                     // if mExpandIcon is null that means user never clicked on the expanded arrow
                     // which implies that the dialog is still not expanded. In that case we do
                     // not want to reset the state
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1f923af..a855741 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -127,6 +127,9 @@
     <!-- Summary for Connected wifi network without internet -->
     <string name="wifi_connected_no_internet">Connected, no internet</string>
 
+    <!-- Summary for connected network without internet due to private dns validation failed [CHAR LIMIT=NONE] -->
+    <string name="private_dns_broken">Private DNS server cannot be accessed</string>
+
     <!-- Summary for connected wifi network with partial internet connectivity [CHAR LIMIT=50] -->
     <string name="wifi_limited_connection">Limited connection</string>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 81d1ea5..16fd51f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -50,6 +50,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -1568,7 +1569,13 @@
                         NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
                     return context.getString(R.string.wifi_limited_connection);
                 } else if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
-                    return context.getString(R.string.wifi_connected_no_internet);
+                    final String mode = Settings.Global.getString(context.getContentResolver(),
+                            Settings.Global.PRIVATE_DNS_MODE);
+                    if (nc.isPrivateDnsBroken()) {
+                        return context.getString(R.string.private_dns_broken);
+                    } else {
+                        return context.getString(R.string.wifi_connected_no_internet);
+                    }
                 }
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 5352936..b11585a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -31,6 +31,7 @@
 import android.net.wifi.WifiSsid;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.Settings;
 
 import com.android.settingslib.R;
 
@@ -163,7 +164,13 @@
                 statusLabel = mContext.getString(R.string.wifi_limited_connection);
                 return;
             } else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
-                statusLabel = mContext.getString(R.string.wifi_status_no_internet);
+                final String mode = Settings.Global.getString(mContext.getContentResolver(),
+                        Settings.Global.PRIVATE_DNS_MODE);
+                if (networkCapabilities.isPrivateDnsBroken()) {
+                    statusLabel = mContext.getString(R.string.private_dns_broken);
+                } else {
+                    statusLabel = mContext.getString(R.string.wifi_status_no_internet);
+                }
                 return;
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 4e6c005..658a0b5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -93,6 +93,7 @@
             if (bssid != null) {
                 visibility.append(" ").append(bssid);
             }
+            visibility.append(" technology = ").append(info.getWifiTechnology());
             visibility.append(" rssi=").append(info.getRssi());
             visibility.append(" ");
             visibility.append(" score=").append(info.score);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8c97057..146f30d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -152,6 +152,8 @@
         Settings.Secure.SILENCE_TIMER_TOUCH_COUNT,
         Settings.Secure.DARK_MODE_DIALOG_SEEN,
         Settings.Secure.GLOBAL_ACTIONS_PANEL_ENABLED,
-        Settings.Secure.AWARE_LOCK_ENABLED
+        Settings.Secure.AWARE_LOCK_ENABLED,
+        Settings.Secure.AWARE_TAP_PAUSE_GESTURE_COUNT,
+        Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index ef67bbd..f7fc0c5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -220,6 +220,8 @@
         VALIDATORS.put(Secure.SILENCE_ALARMS_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.SILENCE_TIMER_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.SILENCE_CALL_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.AWARE_TAP_PAUSE_GESTURE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.AWARE_TAP_PAUSE_TOUCH_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.ODI_CAPTIONS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.DARK_MODE_DIALOG_SEEN, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.UI_NIGHT_MODE, new InclusiveIntegerRangeValidator(0, 2));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 17c621e..44de09b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1497,21 +1497,7 @@
         }
 
         if (upgradeVersion == 94) {
-            // Add wireless charging started sound setting
-            if (mUserHandle == UserHandle.USER_SYSTEM) {
-                db.beginTransaction();
-                SQLiteStatement stmt = null;
-                try {
-                    stmt = db.compileStatement("INSERT OR REPLACE INTO global(name,value)"
-                            + " VALUES(?,?);");
-                    loadStringSetting(stmt, Settings.Global.CHARGING_STARTED_SOUND,
-                            R.string.def_wireless_charging_started_sound);
-                    db.setTransactionSuccessful();
-                } finally {
-                    db.endTransaction();
-                    if (stmt != null) stmt.close();
-                }
-            }
+            // charging sound moved to SettingsProvider version 184
             upgradeVersion = 95;
         }
 
@@ -2562,8 +2548,6 @@
                     R.string.def_car_dock_sound);
             loadStringSetting(stmt, Settings.Global.CAR_UNDOCK_SOUND,
                     R.string.def_car_undock_sound);
-            loadStringSetting(stmt, Settings.Global.CHARGING_STARTED_SOUND,
-                    R.string.def_wireless_charging_started_sound);
 
             loadIntegerSetting(stmt, Settings.Global.DOCK_AUDIO_MEDIA_ENABLED,
                     R.integer.def_dock_audio_media_enabled);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 2ce4e97..e24d387 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1347,6 +1347,9 @@
         dumpSetting(s, p,
                 Settings.Global.CHARGING_STARTED_SOUND,
                 GlobalSettingsProto.Sounds.CHARGING_STARTED);
+        dumpSetting(s, p,
+                Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
+                GlobalSettingsProto.Sounds.WIRELESS_CHARGING_STARTED);
         p.end(soundsToken);
 
         final long soundTriggerToken = p.start(GlobalSettingsProto.SOUND_TRIGGER);
@@ -2047,6 +2050,12 @@
         dumpSetting(s, p,
                 Settings.Secure.SKIP_TOUCH_COUNT,
                 SecureSettingsProto.Gesture.SKIP_TOUCH_COUNT);
+        dumpSetting(s, p,
+                Settings.Secure.AWARE_TAP_PAUSE_GESTURE_COUNT,
+                SecureSettingsProto.Gesture.AWARE_TAP_PAUSE_GESTURE_COUNT);
+        dumpSetting(s, p,
+                Settings.Secure.AWARE_TAP_PAUSE_TOUCH_COUNT,
+                SecureSettingsProto.Gesture.AWARE_TAP_PAUSE_TOUCH_COUNT);
         p.end(gestureToken);
 
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 0639864..80faf476 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3206,7 +3206,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 183;
+            private static final int SETTINGS_VERSION = 184;
 
             private final int mUserId;
 
@@ -3845,23 +3845,7 @@
                 }
 
                 if (currentVersion == 155) {
-                    // Version 156: Set the default value for CHARGING_STARTED_SOUND.
-                    final SettingsState globalSettings = getGlobalSettingsLocked();
-                    final String oldValue = globalSettings.getSettingLocked(
-                            Global.CHARGING_STARTED_SOUND).getValue();
-                    final String oldDefault = getContext().getResources().getString(
-                            R.string.def_wireless_charging_started_sound);
-                    if (TextUtils.equals(null, oldValue)
-                            || TextUtils.equals(oldValue, oldDefault)) {
-                        final String defaultValue = getContext().getResources().getString(
-                                R.string.def_charging_started_sound);
-                        if (!TextUtils.isEmpty(defaultValue)) {
-                            globalSettings.insertSettingLocked(
-                                    Settings.Global.CHARGING_STARTED_SOUND, defaultValue,
-                                    null, true, SettingsState.SYSTEM_PACKAGE_NAME);
-                        }
-
-                    }
+                    // Version 156: migrated to version 184
                     currentVersion = 156;
                 }
 
@@ -4416,6 +4400,48 @@
                     currentVersion = 183;
                 }
 
+                if (currentVersion == 183) {
+                    // Version 184: Set default values for WIRELESS_CHARGING_STARTED_SOUND
+                    // and CHARGING_STARTED_SOUND
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+
+                    final String oldValueWireless = globalSettings.getSettingLocked(
+                            Global.WIRELESS_CHARGING_STARTED_SOUND).getValue();
+                    final String oldValueWired = globalSettings.getSettingLocked(
+                            Global.CHARGING_STARTED_SOUND).getValue();
+
+                    final String defaultValueWireless = getContext().getResources().getString(
+                            R.string.def_wireless_charging_started_sound);
+                    final String defaultValueWired = getContext().getResources().getString(
+                            R.string.def_charging_started_sound);
+
+                    // wireless charging sound
+                    if (oldValueWireless == null
+                            || TextUtils.equals(oldValueWireless, defaultValueWired)) {
+                        if (!TextUtils.isEmpty(defaultValueWireless)) {
+                            globalSettings.insertSettingLocked(
+                                    Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWireless,
+                                    null /* tag */, true /* makeDefault */,
+                                    SettingsState.SYSTEM_PACKAGE_NAME);
+                        } else if (!TextUtils.isEmpty(defaultValueWired)) {
+                            // if the wireless sound is empty, use the wired charging sound
+                            globalSettings.insertSettingLocked(
+                                    Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWired,
+                                    null /* tag */, true /* makeDefault */,
+                                    SettingsState.SYSTEM_PACKAGE_NAME);
+                        }
+                    }
+
+                    // wired charging sound
+                    if (oldValueWired == null && !TextUtils.isEmpty(defaultValueWired)) {
+                        globalSettings.insertSettingLocked(
+                                Global.CHARGING_STARTED_SOUND, defaultValueWired,
+                                null /* tag */, true /* makeDefault */,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+                    currentVersion = 184;
+                }
+
                 // 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 62827bc..179ba8a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -559,6 +559,7 @@
                     Settings.Global.WIFI_WATCHDOG_ON,
                     Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
                     Settings.Global.CHARGING_STARTED_SOUND,
+                    Settings.Global.WIRELESS_CHARGING_STARTED_SOUND,
                     Settings.Global.WINDOW_ANIMATION_SCALE,
                     Settings.Global.WTF_IS_FATAL,
                     Settings.Global.ZEN_MODE,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index e11c063..54e291f 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -247,23 +247,6 @@
             android:exported="false" />
 
         <receiver
-            android:name=".BugreportReceiver"
-            android:permission="android.permission.DUMP">
-            <intent-filter>
-                <action android:name="com.android.internal.intent.action.BUGREPORT_STARTED" />
-                <action android:name="com.android.internal.intent.action.BUGREPORT_FINISHED" />
-            </intent-filter>
-        </receiver>
-
-        <receiver
-            android:name=".RemoteBugreportReceiver"
-            android:permission="android.permission.DUMP">
-            <intent-filter>
-                <action android:name="com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED" />
-            </intent-filter>
-        </receiver>
-
-        <receiver
             android:name=".BugreportRequestedReceiver"
             android:permission="android.permission.TRIGGER_SHELL_BUGREPORT">
             <intent-filter>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 665bde3..1b35770 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -56,16 +56,11 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.IDumpstate;
-import android.os.IDumpstateListener;
-import android.os.IDumpstateToken;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -80,7 +75,6 @@
 import android.view.ContextThemeWrapper;
 import android.view.IWindowManager;
 import android.view.View;
-import android.view.View.OnFocusChangeListener;
 import android.view.WindowManager;
 import android.widget.Button;
 import android.widget.EditText;
@@ -122,31 +116,9 @@
 import java.util.zip.ZipOutputStream;
 
 /**
- * Service used to keep progress of bugreport processes ({@code dumpstate} and
- * {@code BugreportManager}).
+ * Service used to trigger system bugreports.
  * <p>
- * There can be 2 workflows. One workflow via ({@code dumpstate}) is:
- * <ol>
- * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with a sequential id,
- * its pid, and the estimated total effort.
- * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
- * <li>Upon start, this service:
- * <ol>
- * <li>Issues a system notification so user can watch the progress (which is 0% initially).
- * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
- * <li>If the progress changed, it updates the system notification.
- * </ol>
- * <li>As {@code dumpstate} progresses, it updates the system property.
- * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
- * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
- * turn:
- * <ol>
- * <li>Updates the system notification so user can share the bugreport.
- * <li>Stops monitoring that {@code dumpstate} process.
- * <li>Stops itself if it doesn't have any process left to monitor.
- * </ol>
- * </ol>
- * The second workflow using Bugreport API({@code BugreportManager}) is:
+ * The workflow uses Bugreport API({@code BugreportManager}) and is as follows:
  * <ol>
  * <li>System apps like Settings or SysUI broadcasts {@code BUGREPORT_REQUESTED}.
  * <li>{@link BugreportRequestedReceiver} receives the intent and delegates it to this service.
@@ -164,18 +136,14 @@
 
     private static final String AUTHORITY = "com.android.shell";
 
-    // External intents sent by dumpstate.
-    static final String INTENT_BUGREPORT_STARTED =
-            "com.android.internal.intent.action.BUGREPORT_STARTED";
-    static final String INTENT_BUGREPORT_FINISHED =
-            "com.android.internal.intent.action.BUGREPORT_FINISHED";
-    static final String INTENT_REMOTE_BUGREPORT_FINISHED =
-            "com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED";
-
     // External intent used to trigger bugreport API.
     static final String INTENT_BUGREPORT_REQUESTED =
             "com.android.internal.intent.action.BUGREPORT_REQUESTED";
 
+    // Intent sent to notify external apps that bugreport finished
+    static final String INTENT_BUGREPORT_FINISHED =
+            "com.android.internal.intent.action.BUGREPORT_FINISHED";
+
     // Internal intents used on notification actions.
     static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
     static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";
@@ -188,7 +156,6 @@
     static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
     static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
     static final String EXTRA_ID = "android.intent.extra.ID";
-    static final String EXTRA_PID = "android.intent.extra.PID";
     static final String EXTRA_MAX = "android.intent.extra.MAX";
     static final String EXTRA_NAME = "android.intent.extra.NAME";
     static final String EXTRA_TITLE = "android.intent.extra.TITLE";
@@ -218,15 +185,9 @@
      */
     static final int SCREENSHOT_DELAY_SECONDS = 3;
 
-    // TODO: will be gone once fully migrated to Binder
-    /** System properties used to communicate with dumpstate progress. */
-    private static final String DUMPSTATE_PREFIX = "dumpstate.";
-    private static final String NAME_SUFFIX = ".name";
+    /** System property where dumpstate stores last triggered bugreport id */
     private static final String PROPERTY_LAST_ID = "dumpstate.last_id";
 
-    /** System property (and value) used to stop dumpstate. */
-    // TODO: should call ActiveManager API instead
-    private static final String CTL_STOP = "ctl.stop";
     private static final String BUGREPORT_SERVICE = "bugreport";
 
     /**
@@ -273,8 +234,6 @@
 
     private File mScreenshotsDir;
 
-    private boolean mUsingBugreportApi;
-
     private BugreportManager mBugreportManager;
 
     /**
@@ -452,9 +411,9 @@
                 cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE);
                 final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
                 intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
-                addScreenshotToIntent(intent, mInfo);
+                intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo));
                 mContext.sendBroadcast(intent, android.Manifest.permission.DUMP);
-                onBugreportFinished(mInfo.id);
+                onBugreportFinished(mInfo);
             }
         }
     }
@@ -475,14 +434,17 @@
                 android.Manifest.permission.DUMP);
     }
 
-    private static void addScreenshotToIntent(Intent intent, BugreportInfo info) {
-        final File screenshotFile = info.screenshotFiles.isEmpty()
-                ? null : info.screenshotFiles.get(0);
-        if (screenshotFile != null && screenshotFile.length() > 0) {
+    /**
+     * Checks if screenshot array is non-empty and returns the first screenshot's path. The first
+     * screenshot is the default screenshot for the bugreport types that take it.
+     */
+    private static String getScreenshotForIntent(BugreportInfo info) {
+        if (!info.screenshotFiles.isEmpty()) {
+            final File screenshotFile = info.screenshotFiles.get(0);
             final String screenshotFilePath = screenshotFile.getAbsolutePath();
-            intent.putExtra(EXTRA_SCREENSHOT, screenshotFilePath);
+            return screenshotFilePath;
         }
-        return;
+        return null;
     }
 
     private static String generateFileHash(String fileName) {
@@ -558,40 +520,24 @@
             Log.v(TAG, "handleMessage(): " + dumpIntent((Intent) parcel));
             final Intent intent;
             if (parcel instanceof Intent) {
-                // The real intent was passed to BugreportReceiver, which delegated to the service.
+                // The real intent was passed to BugreportRequestedReceiver,
+                // which delegated to the service.
                 intent = (Intent) parcel;
             } else {
                 intent = (Intent) msg.obj;
             }
             final String action = intent.getAction();
-            final int pid = intent.getIntExtra(EXTRA_PID, 0);
             final int id = intent.getIntExtra(EXTRA_ID, 0);
             final int max = intent.getIntExtra(EXTRA_MAX, -1);
             final String name = intent.getStringExtra(EXTRA_NAME);
 
             if (DEBUG)
-                Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id + ", pid: "
-                        + pid + ", max: " + max);
+                Log.v(TAG, "action: " + action + ", name: " + name + ", id: " + id
+                        + ", max: " + max);
             switch (action) {
                 case INTENT_BUGREPORT_REQUESTED:
                     startBugreportAPI(intent);
                     break;
-                case INTENT_BUGREPORT_STARTED:
-                    if (!mUsingBugreportApi && !startProgress(name, id, pid, max)) {
-                        stopSelfWhenDone();
-                        return;
-                    }
-                    break;
-                case INTENT_BUGREPORT_FINISHED:
-                    if (!mUsingBugreportApi) {
-                        if (id == 0) {
-                            // Shouldn't happen, unless BUGREPORT_FINISHED is received
-                            // from a legacy, out-of-sync dumpstate process.
-                            Log.w(TAG, "Missing " + EXTRA_ID + " on intent " + intent);
-                        }
-                        onBugreportFinished(id, intent);
-                    }
-                    break;
                 case INTENT_BUGREPORT_INFO_LAUNCH:
                     launchBugreportInfoDialog(id);
                     break;
@@ -633,52 +579,12 @@
     private BugreportInfo getInfo(int id) {
         final BugreportInfo bugreportInfo = mBugreportInfos.get(id);
         if (bugreportInfo == null) {
-            Log.w(TAG, "Not monitoring process with ID " + id);
+            Log.w(TAG, "Not monitoring bugreports with ID " + id);
             return null;
         }
         return bugreportInfo;
     }
 
-    /**
-     * Creates the {@link BugreportInfo} for a process and issue a system notification to
-     * indicate its progress.
-     *
-     * @return whether it succeeded or not.
-     */
-    private boolean startProgress(String name, int id, int pid, int max) {
-        if (name == null) {
-            Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
-        }
-        if (id == -1) {
-            Log.e(TAG, "Missing " + EXTRA_ID + " on start intent");
-            return false;
-        }
-        if (pid == -1) {
-            Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
-            return false;
-        }
-        if (max <= 0) {
-            Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
-            return false;
-        }
-
-        final BugreportInfo info = new BugreportInfo(mContext, id, pid, name, max);
-        if (mBugreportInfos.indexOfKey(id) >= 0) {
-            // BUGREPORT_STARTED intent was already received; ignore it.
-            Log.w(TAG, "ID " + id + " already watched");
-            return true;
-        }
-        final DumpstateListener listener = new DumpstateListener(info);
-        mBugreportInfos.put(info.id, info);
-        if (listener.connect()) {
-            updateProgress(info);
-            return true;
-        } else {
-            Log.w(TAG, "not updating progress because it could not connect to dumpstate");
-            return false;
-        }
-    }
-
     private String getBugreportBaseName(@BugreportParams.BugreportMode int type) {
         String buildId = SystemProperties.get("ro.build.id", "UNKNOWN_BUILD");
         String deviceName = SystemProperties.get("ro.product.name", "UNKNOWN_DEVICE");
@@ -694,7 +600,6 @@
     }
 
     private void startBugreportAPI(Intent intent) {
-        mUsingBugreportApi = true;
         String shareTitle = intent.getStringExtra(EXTRA_TITLE);
         String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION);
         int bugreportType = intent.getIntExtra(EXTRA_BUGREPORT_TYPE,
@@ -702,10 +607,8 @@
         String baseName = getBugreportBaseName(bugreportType);
         String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
 
-        // pid not used in this workflow, so setting default = 0
-        BugreportInfo info = new BugreportInfo(mContext, 0 /* pid */, baseName, name,
-                100 /* max progress*/, shareTitle, shareDescription, bugreportType,
-                mUsingBugreportApi);
+        BugreportInfo info = new BugreportInfo(mContext, baseName, name,
+                100 /* max progress*/, shareTitle, shareDescription, bugreportType);
 
         ParcelFileDescriptor bugreportFd = info.createBugreportFd();
         if (bugreportFd == null) {
@@ -885,11 +788,7 @@
         final BugreportInfo info = getInfo(id);
         if (info != null && !info.finished) {
             Log.i(TAG, "Cancelling bugreport service (ID=" + id + ") on user's request");
-            if (mUsingBugreportApi) {
-                mBugreportManager.cancelBugreport();
-            } else {
-                setSystemProperty(CTL_STOP, BUGREPORT_SERVICE);
-            }
+            mBugreportManager.cancelBugreport();
             deleteScreenshots(info);
         }
         synchronized (mLock) {
@@ -1087,94 +986,15 @@
     }
 
     /**
-     * Handles the BUGREPORT_FINISHED intent sent by {@code dumpstate}.
-     */
-    private void onBugreportFinished(int id, Intent intent) {
-        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
-        if (bugreportFile == null) {
-            // Should never happen, dumpstate always set the file.
-            Log.wtf(TAG, "Missing " + EXTRA_BUGREPORT + " on intent " + intent);
-            return;
-        }
-        final int max = intent.getIntExtra(EXTRA_MAX, -1);
-        final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
-        final String shareTitle = intent.getStringExtra(EXTRA_TITLE);
-        final String shareDescription = intent.getStringExtra(EXTRA_DESCRIPTION);
-        onBugreportFinished(id, bugreportFile, screenshotFile, shareTitle, shareDescription, max);
-    }
-
-    /**
-     * Handles the onfinish() call by BugreportCallbackImpl using the id
-     */
-    private void onBugreportFinished(int id) {
-        BugreportInfo info = getInfo(id);
-        final File bugreportFile = info.bugreportFile;
-        final int max = -1; // this is to log metrics for dumpstate duration.
-        File screenshotFile = info.screenshotFiles.isEmpty()
-                ? null : info.screenshotFiles.get(0);
-        // If the screenshot file did not get populated implies this type of bugreport does not
-        // need the screenshot file; setting the file to null so that empty file doesnt get shared
-        if (screenshotFile != null && screenshotFile.length() == 0) {
-            if (screenshotFile.delete()) {
-                Log.d(TAG, "screenshot file deleted successfully.");
-            }
-            info.screenshotFiles.remove(0);
-            // TODO(b/136066578): Will soon stop using the below variable as it is a no-op in
-            // API flow and the screenshotFile value is read from info.screenshotFiles
-            screenshotFile = null;
-        }
-        // TODO: Since we are passing id to the function, it should be able to find the info linked
-        // to the id and therefore use the value of shareTitle and shareDescription.
-        onBugreportFinished(id, bugreportFile, screenshotFile, info.shareTitle,
-                info.shareDescription, max);
-    }
-
-
-    /**
      * Wraps up bugreport generation and triggers a notification to share the bugreport.
      */
-    private void onBugreportFinished(int id, File bugreportFile, @Nullable File screenshotFile,
-        String shareTitle, String shareDescription, int max) {
-        mInfoDialog.onBugreportFinished();
-        BugreportInfo info = getInfo(id);
-        if (info == null) {
-            // Happens when BUGREPORT_FINISHED was received without a BUGREPORT_STARTED first.
-            Log.v(TAG, "Creating info for untracked ID " + id);
-            info = new BugreportInfo(mContext, id);
-            DumpstateListener dumpstateListener = new DumpstateListener(info);
-            mBugreportInfos.put(id, info);
-        }
-        if (!mUsingBugreportApi) {
-            // Rename in API flow happens before sending the broadcast for remote bugreport (to
-            // handle renaming before sending broadcasts)
-            info.renameScreenshots(mScreenshotsDir);
-            // API workflow already has the bugreport file. This was required in legacy flow, when
-            // FINISHED broadcast would send the final bugreport files.
-            // TODO(b/136066578): Change function definition to not include bugreport/screenshot
-            // file in params
-            info.bugreportFile = bugreportFile;
-            if (screenshotFile != null) {
-                info.addScreenshot(screenshotFile);
-            }
-        }
-
-        if (max != -1) {
-            MetricsLogger.histogram(this, "dumpstate_duration", max);
-            info.max = max;
-        }
-
-        if (!TextUtils.isEmpty(shareTitle)) {
-            info.title = shareTitle;
-            if (!TextUtils.isEmpty(shareDescription)) {
-                info.shareDescription= shareDescription;
-            }
-            Log.d(TAG, "Bugreport title is " + info.title + ","
-                    + " shareDescription is " + info.shareDescription);
-        }
+    private void onBugreportFinished(BugreportInfo info) {
+        Log.d(TAG, "Bugreport finished with title: " + info.title
+                + " and shareDescription:  " + info.shareDescription);
         info.finished = true;
 
         // Stop running on foreground, otherwise share notification cannot be dismissed.
-        stopForegroundWhenDone(id);
+        stopForegroundWhenDone(info.id);
 
         triggerLocalNotification(mContext, info);
     }
@@ -1214,7 +1034,11 @@
     /**
      * Build {@link Intent} that can be used to share the given bugreport.
      */
-    private static Intent buildSendIntent(Context context, BugreportInfo info) {
+    private static Intent buildSendIntent(Context context, BugreportInfo info,
+            File screenshotsDir) {
+        // Rename files (if required) before sharing
+        info.renameBugreportFile();
+        info.renameScreenshots(screenshotsDir);
         // Files are kept on private storage, so turn into Uris that we can
         // grant temporary permissions for.
         final Uri bugreportUri;
@@ -1300,7 +1124,7 @@
 
         addDetailsToZipFile(info);
 
-        final Intent sendIntent = buildSendIntent(mContext, info);
+        final Intent sendIntent = buildSendIntent(mContext, info, mScreenshotsDir);
         if (sendIntent == null) {
             Log.w(TAG, "Stopping progres on ID " + id + " because share intent could not be built");
             synchronized (mLock) {
@@ -1628,12 +1452,11 @@
         }
         String action = intent.getAction();
         if (action == null) {
-            // Happens when BugreportReceiver calls startService...
+            // Happens when startService is called...
             action = "no action";
         }
         final StringBuilder buffer = new StringBuilder(action).append(" extras: ");
         addExtra(buffer, intent, EXTRA_ID);
-        addExtra(buffer, intent, EXTRA_PID);
         addExtra(buffer, intent, EXTRA_MAX);
         addExtra(buffer, intent, EXTRA_NAME);
         addExtra(buffer, intent, EXTRA_DESCRIPTION);
@@ -1678,15 +1501,6 @@
     }
 
     /**
-     * Updates the system property used by {@code dumpstate} to rename the final bugreport files.
-     */
-    private boolean setBugreportNameProperty(int pid, String name) {
-        Log.d(TAG, "Updating bugreport name to " + name);
-        final String key = DUMPSTATE_PREFIX + pid + NAME_SUFFIX;
-        return setSystemProperty(key, name);
-    }
-
-    /**
      * Updates the user-provided details of a bugreport.
      */
     private void updateBugreportInfo(int id, String name, String title, String description) {
@@ -1770,30 +1584,6 @@
         private AlertDialog mDialog;
         private Button mOkButton;
         private int mId;
-        private int mPid;
-
-        /**
-         * Last "committed" value of the bugreport name.
-         * <p>
-         * Once initially set, it's only updated when user clicks the OK button.
-         */
-        private String mSavedName;
-
-        /**
-         * Last value of the bugreport name as entered by the user.
-         * <p>
-         * Every time it's changed the equivalent system property is changed as well, but if the
-         * user clicks CANCEL, the old value (stored on {@code mSavedName} is restored.
-         * <p>
-         * This logic handles the corner-case scenario where {@code dumpstate} finishes after the
-         * user changed the name but didn't clicked OK yet (for example, because the user is typing
-         * the description). The only drawback is that if the user changes the name while
-         * {@code dumpstate} is running but clicks CANCEL after it finishes, then the final name
-         * will be the one that has been canceled. But when {@code dumpstate} finishes the {code
-         * name} UI is disabled and the old name restored anyways, so the user will be "alerted" of
-         * such drawback.
-         */
-        private String mTempName;
 
         /**
          * Sets its internal state and displays the dialog.
@@ -1813,18 +1603,6 @@
                 mInfoName = (EditText) view.findViewById(R.id.name);
                 mInfoTitle = (EditText) view.findViewById(R.id.title);
                 mInfoDescription = (EditText) view.findViewById(R.id.description);
-
-                mInfoName.setOnFocusChangeListener(new OnFocusChangeListener() {
-
-                    @Override
-                    public void onFocusChange(View v, boolean hasFocus) {
-                        if (hasFocus) {
-                            return;
-                        }
-                        sanitizeName(info);
-                    }
-                });
-
                 mDialog = new AlertDialog.Builder(themedContext)
                         .setView(view)
                         .setTitle(dialogTitle)
@@ -1839,15 +1617,6 @@
                                     {
                                         MetricsLogger.action(context,
                                                 MetricsEvent.ACTION_BUGREPORT_DETAILS_CANCELED);
-                                        if (!mTempName.equals(mSavedName)) {
-                                            // Must restore bugreport's name since it was changed
-                                            // before user clicked OK.
-                                            if (mUsingBugreportApi) {
-                                                info.name = mSavedName;
-                                            } else {
-                                                setBugreportNameProperty(mPid, mSavedName);
-                                            }
-                                        }
                                     }
                                 })
                         .create();
@@ -1866,9 +1635,7 @@
             }
 
             // Then set fields.
-            mSavedName = mTempName = info.name;
             mId = info.id;
-            mPid = info.pid;
             if (!TextUtils.isEmpty(info.name)) {
                 mInfoName.setText(info.name);
             }
@@ -1895,7 +1662,7 @@
                     @Override
                     public void onClick(View view) {
                         MetricsLogger.action(context, MetricsEvent.ACTION_BUGREPORT_DETAILS_SAVED);
-                        sanitizeName(info);
+                        sanitizeName(info.name);
                         final String name = mInfoName.getText().toString();
                         final String title = mInfoTitle.getText().toString();
                         final String description = mInfoDescription.getText().toString();
@@ -1911,9 +1678,9 @@
          * Sanitizes the user-provided value for the {@code name} field, automatically replacing
          * invalid characters if necessary.
          */
-        private void sanitizeName(BugreportInfo info) {
+        private void sanitizeName(String savedName) {
             String name = mInfoName.getText().toString();
-            if (name.equals(mTempName)) {
+            if (name.equals(savedName)) {
                 if (DEBUG) Log.v(TAG, "name didn't change, no need to sanitize: " + name);
                 return;
             }
@@ -1933,28 +1700,6 @@
                 name = safeName.toString();
                 mInfoName.setText(name);
             }
-            mTempName = name;
-            if (mUsingBugreportApi) {
-                info.name = name;
-            } else {
-                // Must update system property for the cases where dumpstate finishes
-                // while the user is still entering other fields (like title or
-                // description)
-                setBugreportNameProperty(mPid, name);
-            }
-        }
-
-       /**
-         * Notifies the dialog that the bugreport has finished so it disables the {@code name}
-         * field.
-         * <p>Once the bugreport is finished dumpstate has already generated the final files, so
-         * changing the name would have no effect.
-         */
-        void onBugreportFinished() {
-            if (mInfoName != null) {
-                mInfoName.setEnabled(false);
-                mInfoName.setText(mSavedName);
-            }
         }
 
         void cancel() {
@@ -1976,18 +1721,10 @@
         int id;
 
         /**
-         * {@code pid} of the {@code dumpstate} process generating the bugreport.
-         * pid is unused in the API flow
-         * TODO(b/136066578): Remove pid
-         */
-        final int pid;
-
-        /**
          * Prefix name of the bugreport, this is uneditable.
          * The baseName consists of the string "bugreport" + deviceName + buildID
          * This will end with the string "wifi"/"telephony" for wifi/telephony bugreports.
          * Bugreport zip file name  = "<baseName>-<name>.zip"
-         * Bugreport png file name = "<baseName>-<name>.png"
          */
         String baseName;
 
@@ -1998,6 +1735,12 @@
         String name;
 
         /**
+         * Initial value of the field name. This is required to rename the files later on, as they
+         * are created using initial value of name.
+         */
+        String initialName;
+
+        /**
          * User-provided, one-line summary of the bug; when set, will be used as the subject
          * of the {@link Intent#ACTION_SEND_MULTIPLE} intent.
          */
@@ -2063,12 +1806,6 @@
         boolean finished;
 
         /**
-         * Whether this bugreport is using API workflow.
-         * TODO(b/136066578): Remove when legacy bugreport methods are removed
-         */
-        boolean usingApi;
-
-        /**
          * Whether the details entries have been added to the bugreport yet.
          */
         boolean addingDetailsToZip;
@@ -2092,42 +1829,18 @@
         int type;
 
         /**
-         * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_STARTED.
-         */
-        BugreportInfo(Context context, int id, int pid, String name, int max) {
-            // bugreports triggered by STARTED broadcast do not use callback functions,
-            // onFinished() callback method is the only function where type is used.
-            // Set type to -1 as it is unused in this workflow.
-            // This constructor will soon be removed.
-            this(context, pid, "" /* dumpstate handles basename */, name, max, null, null, -1,
-                    false);
-            this.id = id;
-        }
-
-        /**
          * Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
          */
-        BugreportInfo(Context context, int pid, String baseName, String name, int max,
+        BugreportInfo(Context context, String baseName, String name, int max,
                 @Nullable String shareTitle, @Nullable String shareDescription,
-                @BugreportParams.BugreportMode int type, boolean usingApi) {
+                @BugreportParams.BugreportMode int type) {
             this.context = context;
-            this.pid = pid;
-            this.name = name;
+            this.name = this.initialName = name;
             this.max = this.realMax = max;
             this.shareTitle = shareTitle == null ? "" : shareTitle;
             this.shareDescription = shareDescription == null ? "" : shareDescription;
             this.type = type;
             this.baseName = baseName;
-            this.usingApi = usingApi;
-        }
-
-        /**
-         * Constructor for untracked bugreports - typically called upon receiving BUGREPORT_FINISHED
-         * without a previous call to BUGREPORT_STARTED.
-         */
-        BugreportInfo(Context context, int id) {
-            this(context, id, id, null, 0);
-            this.finished = true;
         }
 
         ParcelFileDescriptor createBugreportFd() {
@@ -2136,17 +1849,24 @@
         }
 
         ParcelFileDescriptor createScreenshotFd() {
-            File screenshotFile = new File(BUGREPORT_DIR, getFileName(this, ".png"));
+            File screenshotFile = new File(BUGREPORT_DIR, getScreenshotName("default"));
             addScreenshot(screenshotFile);
             return createReadWriteFile(screenshotFile);
         }
 
         /**
-         * Gets the name for next screenshot file.
+         * Gets the name for next user triggered screenshot file.
          */
         String getPathNextScreenshot() {
             screenshotCounter ++;
-            return "screenshot-" + pid + "-" + screenshotCounter + ".png";
+            return getScreenshotName(Integer.toString(screenshotCounter));
+        }
+
+        /**
+         * Gets the name for screenshot file based on the suffix that is passed.
+         */
+        String getScreenshotName(String suffix) {
+            return "screenshot-" + initialName + "-" + suffix + ".png";
         }
 
         /**
@@ -2157,7 +1877,8 @@
         }
 
         /**
-         * Rename all screenshots files so that they contain the user-generated name instead of pid.
+         * Rename all screenshots files so that they contain the new {@code name} instead of the
+         * {@code initialName} if user has changed it.
          */
         void renameScreenshots(File screenshotDir) {
             if (TextUtils.isEmpty(name)) {
@@ -2166,22 +1887,21 @@
             final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
             for (File oldFile : screenshotFiles) {
                 final String oldName = oldFile.getName();
-                final String newName;
-                if (usingApi) {
-                    newName = getFileName(this, ".png");
-                } else {
-                    newName = oldName.replaceFirst(Integer.toString(pid), name);
-                }
+                final String newName = oldName.replaceFirst(initialName, name);
                 final File newFile;
                 if (!newName.equals(oldName)) {
                     final File renamedFile = new File(screenshotDir, newName);
                     Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
                     newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
                 } else {
-                    Log.w(TAG, "Name didn't change: " + oldName); // Shouldn't happen.
+                    Log.w(TAG, "Name didn't change: " + oldName);
                     newFile = oldFile;
                 }
-                renamedFiles.add(newFile);
+                if (newFile.length() > 0) {
+                    renamedFiles.add(newFile);
+                } else if (newFile.delete()) {
+                    Log.d(TAG, "screenshot file: " + newFile + "deleted successfully.");
+                }
             }
             screenshotFiles = renamedFiles;
         }
@@ -2215,11 +1935,10 @@
 
             final StringBuilder builder = new StringBuilder()
                     .append("\tid: ").append(id)
-                    .append(", pid: ").append(pid)
                     .append(", baseName: ").append(baseName)
                     .append(", name: ").append(name)
+                    .append(", initialName: ").append(initialName)
                     .append(", finished: ").append(finished)
-                    .append(", usingApi: ").append(usingApi)
                     .append("\n\ttitle: ").append(title)
                     .append("\n\tdescription: ");
             if (description == null) {
@@ -2250,9 +1969,9 @@
         protected BugreportInfo(Parcel in) {
             context = null;
             id = in.readInt();
-            pid = in.readInt();
             baseName = in.readString();
             name = in.readString();
+            initialName = in.readString();
             title = in.readString();
             description = in.readString();
             max = in.readInt();
@@ -2269,7 +1988,6 @@
             }
 
             finished = in.readInt() == 1;
-            usingApi = in.readInt() == 1;
             screenshotCounter = in.readInt();
             shareDescription = in.readString();
             shareTitle = in.readString();
@@ -2278,9 +1996,9 @@
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(id);
-            dest.writeInt(pid);
             dest.writeString(baseName);
             dest.writeString(name);
+            dest.writeString(initialName);
             dest.writeString(title);
             dest.writeString(description);
             dest.writeInt(max);
@@ -2297,7 +2015,6 @@
             }
 
             dest.writeInt(finished ? 1 : 0);
-            dest.writeInt(usingApi ? 1 : 0);
             dest.writeInt(screenshotCounter);
             dest.writeString(shareDescription);
             dest.writeString(shareTitle);
@@ -2333,80 +2050,6 @@
 
     }
 
-    private final class DumpstateListener extends IDumpstateListener.Stub
-        implements DeathRecipient {
-
-        private final BugreportInfo info;
-        private IDumpstateToken token;
-
-        DumpstateListener(BugreportInfo info) {
-            this.info = info;
-        }
-
-        /**
-         * Connects to the {@code dumpstate} binder to receive updates.
-         */
-        boolean connect() {
-            if (token != null) {
-                Log.d(TAG, "connect(): " + info.id + " already connected");
-                return true;
-            }
-            final IBinder service = ServiceManager.getService("dumpstate");
-            if (service == null) {
-                Log.d(TAG, "dumpstate service not bound yet");
-                return true;
-            }
-            final IDumpstate dumpstate = IDumpstate.Stub.asInterface(service);
-            try {
-                token = dumpstate.setListener("Shell", this, /* perSectionDetails= */ false);
-                if (token != null) {
-                    token.asBinder().linkToDeath(this, 0);
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "Could not set dumpstate listener: " + e);
-            }
-            return token != null;
-        }
-
-        @Override
-        public void binderDied() {
-            if (!info.finished) {
-                // TODO: linkToDeath() might be called BEFORE Shell received the
-                // BUGREPORT_FINISHED broadcast, in which case the statements below
-                // spam logcat (but are harmless).
-                // The right, long-term solution is to provide an onFinished() callback
-                // on IDumpstateListener and call it instead of using a broadcast.
-                Log.w(TAG, "Dumpstate process died:\n" + info);
-                synchronized (mLock) {
-                    stopProgressLocked(info.id);
-                }
-            }
-            token.asBinder().unlinkToDeath(this, 0);
-        }
-
-        @Override
-        public void onProgress(int progress) throws RemoteException {
-            synchronized (mLock) {
-                checkProgressUpdatedLocked(info, progress);
-            }
-        }
-
-        @Override
-        public void onError(int errorCode) throws RemoteException {
-            // TODO(b/111441001): implement
-        }
-
-        @Override
-        public void onFinished() throws RemoteException {
-            // TODO(b/111441001): implement
-        }
-
-        public void dump(String prefix, PrintWriter pw) {
-            pw.print(prefix); pw.print("token: "); pw.println(token);
-        }
-
-    }
-
     @GuardedBy("mLock")
     private void checkProgressUpdatedLocked(BugreportInfo info, int progress) {
         if (progress > CAPPED_PROGRESS) {
@@ -2418,11 +2061,11 @@
     private void updateProgressInfo(BugreportInfo info, int progress, int max) {
         if (DEBUG) {
             if (progress != info.progress) {
-                Log.v(TAG, "Updating progress for PID " + info.pid + "(id: " + info.id
+                Log.v(TAG, "Updating progress for name " + info.name + "(id: " + info.id
                         + ") from " + info.progress + " to " + progress);
             }
             if (max != info.max) {
-                Log.v(TAG, "Updating max progress for PID " + info.pid + "(id: " + info.id
+                Log.v(TAG, "Updating max progress for name " + info.name + "(id: " + info.id
                         + ") from " + info.max + " to " + max);
             }
         }
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
deleted file mode 100644
index 15ce90f..0000000
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.shell;
-
-import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
-import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT;
-import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
-import static com.android.shell.BugreportProgressService.getFileExtra;
-import static com.android.shell.BugreportProgressService.dumpIntent;
-
-import java.io.File;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.FileUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-/**
- * Receiver that handles finished bugreports, usually by attaching them to an
- * {@link Intent#ACTION_SEND_MULTIPLE}.
- */
-public class BugreportReceiver extends BroadcastReceiver {
-    private static final String TAG = "BugreportReceiver";
-
-    /**
-     * Always keep the newest 8 bugreport files.
-     */
-    private static final int MIN_KEEP_COUNT = 8;
-
-    /**
-     * Always keep bugreports taken in the last week.
-     */
-    private static final long MIN_KEEP_AGE = DateUtils.WEEK_IN_MILLIS;
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.d(TAG, "onReceive(): " + dumpIntent(intent));
-        // Clean up older bugreports in background
-        cleanupOldFiles(this, intent, INTENT_BUGREPORT_FINISHED, MIN_KEEP_COUNT, MIN_KEEP_AGE);
-
-        // Delegate intent handling to service.
-        Intent serviceIntent = new Intent(context, BugreportProgressService.class);
-        serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
-        context.startService(serviceIntent);
-    }
-
-    static void cleanupOldFiles(BroadcastReceiver br, Intent intent, String expectedAction,
-            final int minCount, final long minAge) {
-        if (!expectedAction.equals(intent.getAction())) {
-            return;
-        }
-        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
-        if (bugreportFile == null || !bugreportFile.exists()) {
-            Log.e(TAG, "Not deleting old files because file " + bugreportFile + " doesn't exist");
-            return;
-        }
-        final PendingResult result = br.goAsync();
-        new AsyncTask<Void, Void, Void>() {
-            @Override
-            protected Void doInBackground(Void... params) {
-                try {
-                    FileUtils.deleteOlderFiles(bugreportFile.getParentFile(), minCount, minAge);
-                } catch (RuntimeException e) {
-                    Log.e(TAG, "RuntimeException deleting old files", e);
-                }
-                result.finish();
-                return null;
-            }
-        }.execute();
-    }
-}
diff --git a/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java b/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java
deleted file mode 100644
index 634c3b4..0000000
--- a/packages/Shell/src/com/android/shell/RemoteBugreportReceiver.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 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.shell;
-
-import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
-import static com.android.shell.BugreportProgressService.INTENT_REMOTE_BUGREPORT_FINISHED;
-import static com.android.shell.BugreportProgressService.getFileExtra;
-import static com.android.shell.BugreportProgressService.getUri;
-import static com.android.shell.BugreportReceiver.cleanupOldFiles;
-
-import java.io.File;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-
-/**
- * Receiver that handles finished remote bugreports, by re-sending
- * the intent with appended bugreport zip file URI.
- *
- * <p> Remote bugreport never contains a screenshot.
- */
-public class RemoteBugreportReceiver extends BroadcastReceiver {
-
-    private static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
-
-    /** Always keep just the last remote bugreport's files around. */
-    private static final int REMOTE_BUGREPORT_FILES_AMOUNT = 3;
-
-    /** Always keep remote bugreport files created in the last day. */
-    private static final long MIN_KEEP_AGE = DateUtils.DAY_IN_MILLIS;
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        cleanupOldFiles(this, intent, INTENT_REMOTE_BUGREPORT_FINISHED,
-                REMOTE_BUGREPORT_FILES_AMOUNT, MIN_KEEP_AGE);
-
-        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
-        final Uri bugreportUri = getUri(context, bugreportFile);
-        final String bugreportHash = intent.getStringExtra(
-                DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH);
-
-        final Intent newIntent = new Intent(DevicePolicyManager.ACTION_REMOTE_BUGREPORT_DISPATCH);
-        newIntent.setDataAndType(bugreportUri, BUGREPORT_MIMETYPE);
-        newIntent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
-        context.sendBroadcastAsUser(newIntent, UserHandle.SYSTEM,
-                android.Manifest.permission.DUMP);
-    }
-}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 3a71632..bb298e9 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -29,10 +29,8 @@
 import static com.android.shell.BugreportProgressService.EXTRA_ID;
 import static com.android.shell.BugreportProgressService.EXTRA_MAX;
 import static com.android.shell.BugreportProgressService.EXTRA_NAME;
-import static com.android.shell.BugreportProgressService.EXTRA_PID;
 import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
 import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
-import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_STARTED;
 import static com.android.shell.BugreportProgressService.SCREENSHOT_DELAY_SECONDS;
 
 import static org.junit.Assert.assertEquals;
@@ -145,6 +143,10 @@
     private static final String TITLE2 = "Master of the Universe";
     private static final String DESCRIPTION = "One's description...";
     private static final String DESCRIPTION2 = "...is another's treasure.";
+    // TODO(b/143130523): Fix (update) tests and add to presubmit
+    private static final String EXTRA_PID = "android.intent.extra.PID";
+    private static final String INTENT_BUGREPORT_STARTED =
+            "com.android.internal.intent.action.BUGREPORT_STARTED";
 
     private static final String NO_DESCRIPTION = null;
     private static final String NO_NAME = null;
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
new file mode 100644
index 0000000..802a8da
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NotificationPersonExtractorPlugin.java
@@ -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.systemui.plugins;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.graphics.drawable.Drawable;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.plugins.annotations.DependsOn;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/** Custom logic that can extract a PeopleHub "person" from a notification. */
+@ProvidesInterface(
+        action = NotificationPersonExtractorPlugin.ACTION,
+        version = NotificationPersonExtractorPlugin.VERSION)
+@DependsOn(target = NotificationPersonExtractorPlugin.PersonData.class)
+public interface NotificationPersonExtractorPlugin extends Plugin {
+
+    String ACTION = "com.android.systemui.action.PEOPLE_HUB_PERSON_EXTRACTOR";
+    int VERSION = 0;
+
+    /**
+     * Attempts to extract a person from a notification. Returns {@code null} if one is not found.
+     */
+    @Nullable PersonData extractPerson(StatusBarNotification sbn);
+
+    /**
+     * Attempts to extract a person id from a notification. Returns {@code null} if one is not
+     * found.
+     *
+     * This method can be overridden in order to provide a faster implementation.
+     */
+    @Nullable
+    default String extractPersonKey(StatusBarNotification sbn) {
+        return extractPerson(sbn).key;
+    }
+
+    /** A person to be surfaced in PeopleHub. */
+    @ProvidesInterface(version = PersonData.VERSION)
+    final class PersonData {
+
+        public static final int VERSION = 0;
+
+        public final String key;
+        public final CharSequence name;
+        public final Drawable avatar;
+        public final PendingIntent clickIntent;
+
+        public PersonData(String key, CharSequence name, Drawable avatar,
+                PendingIntent clickIntent) {
+            this.key = key;
+            this.name = name;
+            this.avatar = avatar;
+            this.clickIntent = clickIntent;
+        }
+    }
+}
diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml
index b314db8..f0ac08b 100644
--- a/packages/SystemUI/res/layout/people_strip.xml
+++ b/packages/SystemUI/res/layout/people_strip.xml
@@ -33,10 +33,9 @@
     <LinearLayout
         android:id="@+id/people_list"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:paddingTop="8dp"
-        android:paddingBottom="8dp"
+        android:layout_height="match_parent"
+        android:paddingTop="12dp"
+        android:paddingBottom="12dp"
         android:gravity="center"
         android:orientation="horizontal">
 
@@ -49,7 +48,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
@@ -87,7 +86,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
@@ -125,7 +124,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
@@ -163,7 +162,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
@@ -201,7 +200,7 @@
         <LinearLayout
             android:layout_width="70dp"
             android:layout_height="match_parent"
-            android:gravity="center"
+            android:gravity="center_horizontal"
             android:orientation="vertical"
             android:visibility="invisible">
 
diff --git a/packages/SystemUI/res/layout/qqs_media_panel.xml b/packages/SystemUI/res/layout/qqs_media_panel.xml
new file mode 100644
index 0000000..1189371
--- /dev/null
+++ b/packages/SystemUI/res/layout/qqs_media_panel.xml
@@ -0,0 +1,100 @@
+<?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
+  -->
+
+<!-- Layout for QQS media controls -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/qqs_media_controls"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:gravity="center"
+    android:padding="10dp"
+    >
+    <!-- Top line: icon + artist name -->
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:gravity="center"
+        >
+        <com.android.internal.widget.CachingIconView
+            android:id="@+id/icon"
+            android:layout_width="15dp"
+            android:layout_height="15dp"
+            android:layout_marginEnd="5dp"
+        />
+        <TextView
+            android:id="@+id/header_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:singleLine="true"
+        />
+    </LinearLayout>
+
+    <!-- Second line: song name -->
+    <TextView
+        android:id="@+id/header_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:gravity="center"/>
+
+    <!-- Bottom section: controls -->
+    <LinearLayout
+        android:id="@+id/media_actions"
+        android:orientation="horizontal"
+        android:layoutDirection="ltr"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        >
+        <ImageButton
+            style="@android:style/Widget.Material.Button.Borderless.Small"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:layout_marginEnd="2dp"
+            android:gravity="center"
+            android:visibility="gone"
+            android:id="@+id/action0"
+        />
+        <ImageButton
+            style="@android:style/Widget.Material.Button.Borderless.Small"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:layout_marginEnd="2dp"
+            android:gravity="center"
+            android:visibility="gone"
+            android:id="@+id/action1"
+        />
+        <ImageButton
+            style="@android:style/Widget.Material.Button.Borderless.Small"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:layout_marginEnd="2dp"
+            android:gravity="center"
+            android:visibility="gone"
+            android:id="@+id/action2"
+        />
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml
new file mode 100644
index 0000000..dd42276
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_media_panel.xml
@@ -0,0 +1,124 @@
+<?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
+  -->
+
+<!-- Layout for media controls inside QSPanel carousel -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/qs_media_controls"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:gravity="center_horizontal|fill_vertical"
+    android:padding="10dp"
+    >
+
+    <!-- placeholder for notification header -->
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/header"
+        android:padding="3dp"
+        android:layout_marginEnd="-12dp"
+        />
+
+    <!-- Top line: artist name -->
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        >
+        <TextView
+            android:id="@+id/header_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:singleLine="true"
+        />
+    </LinearLayout>
+
+    <!-- Second line: song name -->
+    <TextView
+        android:id="@+id/header_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:fontFamily="@*android:string/config_bodyFontFamily"
+        android:gravity="center"/>
+
+    <!-- Bottom section: controls -->
+    <LinearLayout
+        android:id="@+id/media_actions"
+        android:orientation="horizontal"
+        android:layoutDirection="ltr"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        >
+        <ImageButton
+            style="@android:style/Widget.Material.Button.Borderless.Small"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:layout_marginEnd="2dp"
+            android:gravity="center"
+            android:visibility="gone"
+            android:id="@+id/action0"
+        />
+        <ImageButton
+            style="@android:style/Widget.Material.Button.Borderless.Small"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:layout_marginEnd="2dp"
+            android:gravity="center"
+            android:visibility="gone"
+            android:id="@+id/action1"
+        />
+        <ImageButton
+            style="@android:style/Widget.Material.Button.Borderless.Small"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:layout_marginEnd="2dp"
+            android:gravity="center"
+            android:visibility="gone"
+            android:id="@+id/action2"
+        />
+        <ImageButton
+            style="@android:style/Widget.Material.Button.Borderless.Small"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:layout_marginEnd="2dp"
+            android:gravity="center"
+            android:visibility="gone"
+            android:id="@+id/action3"
+        />
+        <ImageButton
+            style="@android:style/Widget.Material.Button.Borderless.Small"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:layout_marginEnd="2dp"
+            android:gravity="center"
+            android:visibility="gone"
+            android:id="@+id/action4"
+        />
+    </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index ed18dc7..e99b917 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -43,7 +43,7 @@
     <com.android.systemui.qs.QuickQSPanel
         android:id="@+id/quick_qs_panel"
         android:layout_width="match_parent"
-        android:layout_height="48dp"
+        android:layout_height="wrap_content"
         android:layout_below="@id/quick_qs_status_icons"
         android:layout_marginStart="@dimen/qs_header_tile_margin_horizontal"
         android:layout_marginEnd="@dimen/qs_header_tile_margin_horizontal"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d722d61..14371fe 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1164,4 +1164,11 @@
 
     <!-- Size of the RAT type for CellularTile -->
     <dimen name="celltile_rat_type_size">10sp</dimen>
+
+    <dimen name="new_qs_vertical_margin">8dp</dimen>
+
+    <!-- Size of media cards in the QSPanel carousel -->
+    <dimen name="qs_media_height">150dp</dimen>
+    <dimen name="qs_media_width">350dp</dimen>
+    <dimen name="qs_media_padding">8dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3cc683a..0082949 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -461,13 +461,13 @@
     <string name="data_connection_lte_plus">LTE+</string>
 
     <!-- Content description of the data connection type 5Ge. [CHAR LIMIT=NONE] -->
-    <string name="data_connection_5ge" translate="false">5Ge</string>
+    <string name="data_connection_5ge" translatable="false">5Ge</string>
 
     <!-- Content description of the data connection type 5G. [CHAR LIMIT=NONE] -->
-    <string name="data_connection_5g" translate="false">5G</string>
+    <string name="data_connection_5g" translatable="false">5G</string>
 
     <!-- Content description of the data connection type 5G+. [CHAR LIMIT=NONE] -->
-    <string name="data_connection_5g_plus" translate="false">5G+</string>
+    <string name="data_connection_5g_plus" translatable="false">5G+</string>
 
     <!-- Content description of the data connection type CDMA. [CHAR LIMIT=NONE] -->
     <string name="data_connection_cdma">1X</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index ebac3d9..caee8cc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -131,8 +131,7 @@
     protected void verifyPasswordAndUnlock() {
         if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
 
-        final LockscreenCredential password =
-                LockscreenCredential.createPassword(getPasswordText());
+        final LockscreenCredential password = getEnteredCredential();
         setPasswordEntryInputEnabled(false);
         if (mPendingLockCheck != null) {
             mPendingLockCheck.cancel(false);
@@ -223,7 +222,7 @@
     }
 
     protected abstract void resetPasswordText(boolean animate, boolean announce);
-    protected abstract CharSequence getPasswordText();
+    protected abstract LockscreenCredential getEnteredCredential();
     protected abstract void setPasswordEntryEnabled(boolean enabled);
     protected abstract void setPasswordEntryInputEnabled(boolean enabled);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 12c9fc9..f8f3dc8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -36,6 +36,7 @@
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.TextViewInputDisabler;
 import com.android.systemui.R;
 
@@ -243,8 +244,8 @@
     }
 
     @Override
-    protected CharSequence getPasswordText() {
-        return mPasswordEntry.getText();
+    protected LockscreenCredential getEnteredCredential() {
+        return LockscreenCredential.createPasswordOrNone(mPasswordEntry.getText());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 8e9df55..c67decc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -23,6 +23,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.internal.widget.LockscreenCredential;
 import com.android.systemui.R;
 
 /**
@@ -167,8 +168,8 @@
     }
 
     @Override
-    protected CharSequence getPasswordText() {
-        return mPasswordEntry.getText();
+    protected LockscreenCredential getEnteredCredential() {
+        return LockscreenCredential.createPinOrNone(mPasswordEntry.getText());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ac5e255..27410be 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -39,7 +39,6 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
@@ -101,6 +100,7 @@
 import com.android.settingslib.WirelessUtils;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainLooper;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -118,7 +118,6 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -1500,7 +1499,7 @@
 
     @VisibleForTesting
     @Inject
-    protected KeyguardUpdateMonitor(Context context, @Named(MAIN_LOOPER_NAME) Looper mainLooper) {
+    protected KeyguardUpdateMonitor(Context context, @MainLooper Looper mainLooper) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index ff8a932..486d02c 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -40,6 +40,10 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.MainLooper;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.ScreenLifecycle;
@@ -148,16 +152,16 @@
     /**
      * Key for getting a the main looper.
      */
-    public static final String MAIN_LOOPER_NAME = "main_looper";
+    private static final String MAIN_LOOPER_NAME = "main_looper";
 
     /**
      * Key for getting a background Looper for background work.
      */
-    public static final String BG_LOOPER_NAME = "background_looper";
+    private static final String BG_LOOPER_NAME = "background_looper";
     /**
      * Key for getting a background Handler for background work.
      */
-    public static final String BG_HANDLER_NAME = "background_handler";
+    private static final String BG_HANDLER_NAME = "background_handler";
     /**
      * Key for getting a Handler for receiving time tick broadcasts on.
      */
@@ -165,7 +169,7 @@
     /**
      * Generic handler on the main thread.
      */
-    public static final String MAIN_HANDLER_NAME = "main_handler";
+    private static final String MAIN_HANDLER_NAME = "main_handler";
 
     /**
      * An email address to send memory leak reports to by default.
@@ -300,10 +304,10 @@
     @Inject Lazy<AutoHideController> mAutoHideController;
     @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
     @Inject Lazy<PrivacyItemController> mPrivacyItemController;
-    @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
-    @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
-    @Inject @Named(MAIN_LOOPER_NAME) Lazy<Looper> mMainLooper;
-    @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
+    @Inject @BgLooper Lazy<Looper> mBgLooper;
+    @Inject @BgHandler Lazy<Handler> mBgHandler;
+    @Inject @MainLooper Lazy<Looper> mMainLooper;
+    @Inject @MainHandler Lazy<Handler> mMainHandler;
     @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
     @Nullable
     @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index aa13fa8..746515a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -27,6 +27,8 @@
 import androidx.annotation.Nullable;
 import androidx.core.app.AppComponentFactory;
 
+import com.android.systemui.dagger.ContextComponentHelper;
+
 import javax.inject.Inject;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 91776a3..022bf06 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -34,6 +34,7 @@
 import android.util.Log;
 import android.util.TimingsTraceLog;
 
+import com.android.systemui.dagger.ContextComponentHelper;
 import com.android.systemui.plugins.OverlayPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.plugins.PluginManager;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 11db481..0a547b6 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -27,6 +27,9 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.dagger.DaggerSystemUIRootComponent;
+import com.android.systemui.dagger.DependencyProvider;
+import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -107,7 +110,7 @@
 
     protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
         return DaggerSystemUIRootComponent.builder()
-                .dependencyProvider(new com.android.systemui.DependencyProvider())
+                .dependencyProvider(new DependencyProvider())
                 .contextHolder(new ContextHolder(context))
                 .build();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index ef171d3..f616d57 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.appops;
 
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -31,6 +29,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.BgLooper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -39,7 +38,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -79,7 +77,7 @@
     };
 
     @Inject
-    public AppOpsControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper) {
+    public AppOpsControllerImpl(Context context, @BgLooper Looper bgLooper) {
         this(context, bgLooper, new PermissionFlagsCache(context));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index cdc2623..b758731 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.biometrics;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
@@ -27,6 +30,8 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -34,6 +39,7 @@
 import android.util.Log;
 import android.view.WindowManager;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.systemui.SystemUI;
@@ -229,15 +235,8 @@
     }
 
     @Override
-    public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
-        if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: " + authenticated
-                + " reason: " + failureReason);
-
-        if (authenticated) {
-            mCurrentDialog.onAuthenticationSucceeded();
-        } else {
-            mCurrentDialog.onAuthenticationFailed(failureReason);
-        }
+    public void onBiometricAuthenticated() {
+        mCurrentDialog.onAuthenticationSucceeded();
     }
 
     @Override
@@ -247,16 +246,45 @@
         mCurrentDialog.onHelp(message);
     }
 
-    @Override
-    public void onBiometricError(int errorCode, String error) {
-        if (DEBUG) Log.d(TAG, "onBiometricError: " + errorCode + ", " + error);
+    private String getErrorString(int modality, int error, int vendorCode) {
+        switch (modality) {
+            case TYPE_FACE:
+                return FaceManager.getErrorString(mContext, error, vendorCode);
 
-        final boolean isLockout = errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
-                || errorCode == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+            case TYPE_FINGERPRINT:
+                return FingerprintManager.getErrorString(mContext, error, vendorCode);
+
+            default:
+                return "";
+        }
+    }
+
+    @Override
+    public void onBiometricError(int modality, int error, int vendorCode) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
+        }
+
+        final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
+                || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
+
+        // TODO(b/141025588): Create separate methods for handling hard and soft errors.
+        final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
+                || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
+
         if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
+            if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
             mCurrentDialog.animateToCredentialUI();
+        } else if (isSoftError) {
+            final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
+                    ? mContext.getString(R.string.biometric_not_recognized)
+                    : getErrorString(modality, error, vendorCode);
+            if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
+            mCurrentDialog.onAuthenticationFailed(errorMessage);
         } else {
-            mCurrentDialog.onError(error);
+            final String errorMessage = getErrorString(modality, error, vendorCode);
+            if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
+            mCurrentDialog.onError(errorMessage);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 5e977b4..ff4711c 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -26,13 +26,12 @@
 import android.util.Log
 import android.util.SparseArray
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.Dependency.BG_LOOPER_NAME
-import com.android.systemui.Dependency.MAIN_HANDLER_NAME
 import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.BgLooper
+import com.android.systemui.dagger.qualifiers.MainHandler
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import javax.inject.Inject
-import javax.inject.Named
 import javax.inject.Singleton
 
 data class ReceiverData(
@@ -61,8 +60,8 @@
 @Singleton
 open class BroadcastDispatcher @Inject constructor (
     private val context: Context,
-    @Named(MAIN_HANDLER_NAME) private val mainHandler: Handler,
-    @Named(BG_LOOPER_NAME) private val bgLooper: Looper
+    @MainHandler private val mainHandler: Handler,
+    @BgLooper private val bgLooper: Looper
 ) : Dumpable {
 
     // Only modify in BG thread
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 914258f..db85fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -17,7 +17,6 @@
 package com.android.systemui.classifier;
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_MANAGER_ENABLED;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
 
 import android.content.Context;
 import android.hardware.SensorManager;
@@ -31,6 +30,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
 import com.android.systemui.classifier.brightline.FalsingDataProvider;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingPlugin;
 import com.android.systemui.plugins.PluginListener;
@@ -41,7 +41,6 @@
 import java.io.PrintWriter;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -62,7 +61,7 @@
 
     @Inject
     FalsingManagerProxy(Context context, PluginManager pluginManager,
-            @Named(MAIN_HANDLER_NAME) Handler handler,
+            @MainHandler Handler handler,
             ProximitySensor proximitySensor,
             DeviceConfigProxy deviceConfig) {
         mProximitySensor = proximitySensor;
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/ActivityBinder.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/ActivityBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ActivityBinder.java
index 2c8a672..4be610f 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ActivityBinder.java
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
 import android.app.Activity;
 
+import com.android.systemui.ForegroundServicesDialog;
 import com.android.systemui.tuner.TunerActivity;
 
 import dagger.Binds;
diff --git a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/ComponentBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
index 3b35c61..4e4c06e 100644
--- a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ComponentBinder.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
 import dagger.Binds;
 import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
index 2cf0f8d..d6d1e41 100644
--- a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
 import android.app.Activity;
 import android.app.Service;
 
+import com.android.systemui.SystemUI;
+
 /**
  * Interface necessary to make Dagger happy. See {@link ContextComponentResolver}.
  */
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
index 9952632..d7822c9 100644
--- a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
 import android.app.Activity;
 import android.app.Service;
 
+import com.android.systemui.SystemUI;
+
 import java.util.Map;
 
 import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/DependencyBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index f9f0f1b..6674c12 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
+import com.android.systemui.ActivityStarterDelegate;
 import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.appops.AppOpsControllerImpl;
 import com.android.systemui.classifier.FalsingManagerProxy;
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/DependencyProvider.java
rename to packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 8c1f8ac..87434f3 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,51 +14,35 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
-import static com.android.systemui.Dependency.BG_HANDLER_NAME;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.IActivityManager;
 import android.app.INotificationManager;
-import android.app.IWallpaperManager;
-import android.app.WallpaperManager;
 import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.SensorPrivacyManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.PowerManager;
 import android.os.Process;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.view.IWindowManager;
-import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.MainHandler;
+import com.android.systemui.dagger.qualifiers.MainLooper;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.plugins.PluginInitializerImpl;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
-import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -70,11 +54,7 @@
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.util.leak.LeakDetector;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
 import javax.inject.Named;
-import javax.inject.Qualifier;
 import javax.inject.Singleton;
 
 import dagger.Module;
@@ -82,16 +62,14 @@
 
 /**
  * Provides dependencies for the root component of sysui injection.
+ *
+ * Only SystemUI owned classes and instances should go in here. Other, framework-owned classes
+ * should go in {@link SystemServicesModule}.
+ *
  * See SystemUI/docs/dagger.md
  */
 @Module
 public class DependencyProvider {
-    @Qualifier
-    @Documented
-    @Retention(RUNTIME)
-    public @interface MainResources {
-        // TODO: use attribute to get other, non-main resources?
-    }
 
     @Singleton
     @Provides
@@ -104,7 +82,7 @@
 
     @Singleton
     @Provides
-    @Named(BG_LOOPER_NAME)
+    @BgLooper
     public Looper provideBgLooper() {
         HandlerThread thread = new HandlerThread("SysUiBg",
                 Process.THREAD_PRIORITY_BACKGROUND);
@@ -113,29 +91,34 @@
     }
 
     /** Main Looper */
-    @Singleton
     @Provides
-    @Named(MAIN_LOOPER_NAME)
+    @MainLooper
     public Looper provideMainLooper() {
         return Looper.getMainLooper();
     }
 
     @Singleton
     @Provides
-    @Named(BG_HANDLER_NAME)
-    public Handler provideBgHandler(@Named(BG_LOOPER_NAME) Looper bgLooper) {
+    @BgHandler
+    public Handler provideBgHandler(@BgLooper Looper bgLooper) {
         return new Handler(bgLooper);
     }
 
     @Singleton
     @Provides
-    @Named(MAIN_HANDLER_NAME)
-    public Handler provideMainHandler(@Named(MAIN_LOOPER_NAME) Looper mainLooper) {
+    @MainHandler
+    public Handler provideMainHandler(@MainLooper Looper mainLooper) {
         return new Handler(mainLooper);
     }
 
     /** */
     @Provides
+    public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) {
+        return new AmbientDisplayConfiguration(context);
+    }
+
+    /** */
+    @Provides
     public Handler provideHandler() {
         return new Handler();
     }
@@ -148,23 +131,10 @@
 
     @Singleton
     @Provides
-    @Nullable
-    public LocalBluetoothManager provideLocalBluetoothController(Context context,
-            @Named(BG_HANDLER_NAME) Handler bgHandler) {
-        return LocalBluetoothManager.create(context, bgHandler,
-                UserHandle.ALL);
-    }
-
-    @Singleton
-    @Provides
-    public MetricsLogger provideMetricsLogger() {
-        return new MetricsLogger();
-    }
-
-    @Singleton
-    @Provides
-    public IWindowManager provideIWindowManager() {
-        return WindowManagerGlobal.getWindowManagerService();
+    // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used
+    // anywhere it is needed.
+    public DisplayMetrics provideDisplayMetrics() {
+        return new DisplayMetrics();
     }
 
     @Singleton
@@ -184,20 +154,6 @@
 
     @Singleton
     @Provides
-    // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used
-    // anywhere it is needed.
-    public DisplayMetrics provideDisplayMetrics() {
-        return new DisplayMetrics();
-    }
-
-    @Singleton
-    @Provides
-    public SensorPrivacyManager provideSensorPrivacyManager(Context context) {
-        return context.getSystemService(SensorPrivacyManager.class);
-    }
-
-    @Singleton
-    @Provides
     public LeakDetector provideLeakDetector() {
         return LeakDetector.create();
 
@@ -205,8 +161,14 @@
 
     @Singleton
     @Provides
+    public MetricsLogger provideMetricsLogger() {
+        return new MetricsLogger();
+    }
+
+    @Singleton
+    @Provides
     public NightDisplayListener provideNightDisplayListener(Context context,
-            @Named(BG_HANDLER_NAME) Handler bgHandler) {
+            @BgHandler Handler bgHandler) {
         return new NightDisplayListener(context, bgHandler);
     }
 
@@ -219,7 +181,7 @@
     @Singleton
     @Provides
     public NavigationBarController provideNavigationBarController(Context context,
-            @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+            @MainHandler Handler mainHandler) {
         return new NavigationBarController(context, mainHandler);
     }
 
@@ -232,7 +194,7 @@
     @Singleton
     @Provides
     public AutoHideController provideAutoHideController(Context context,
-            @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+            @MainHandler Handler mainHandler,
             NotificationRemoteInputManager notificationRemoteInputManager,
             IWindowManager iWindowManager) {
         return new AutoHideController(context, mainHandler, notificationRemoteInputManager,
@@ -253,25 +215,12 @@
 
     @Singleton
     @Provides
-    public PackageManagerWrapper providePackageManagerWrapper() {
-        return PackageManagerWrapper.getInstance();
-    }
-
-    @Singleton
-    @Provides
     public DeviceProvisionedController provideDeviceProvisionedController(Context context,
-            @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+            @MainHandler Handler mainHandler) {
         return new DeviceProvisionedControllerImpl(context, mainHandler);
     }
 
     /** */
-    @Singleton
-    @Provides
-    public AlarmManager provideAlarmManager(Context context) {
-        return context.getSystemService(AlarmManager.class);
-    }
-
-    /** */
     @Provides
     public LockPatternUtils provideLockPatternUtils(Context context) {
         return new LockPatternUtils(context);
@@ -279,52 +228,7 @@
 
     /** */
     @Provides
-    public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) {
-        return new AmbientDisplayConfiguration(context);
-    }
-
-    /** */
-    @Provides
     public AlwaysOnDisplayPolicy provideAlwaysOnDisplayPolicy(Context context) {
         return new AlwaysOnDisplayPolicy(context);
     }
-
-    /** */
-    @Provides
-    public PowerManager providePowerManager(Context context) {
-        return context.getSystemService(PowerManager.class);
-    }
-
-    /** */
-    @Provides
-    @MainResources
-    public Resources provideResources(Context context) {
-        return context.getResources();
-    }
-
-    /** */
-    @Provides
-    @Nullable
-    public IWallpaperManager provideIWallpaperManager() {
-        return IWallpaperManager.Stub.asInterface(
-                ServiceManager.getService(Context.WALLPAPER_SERVICE));
-    }
-
-    /** */
-    @Provides
-    public WallpaperManager providesWallpaperManager(Context context) {
-        return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
-    }
-
-    /** */
-    @Provides
-    public WindowManager providesWindowManager(Context context) {
-        return context.getSystemService(WindowManager.class);
-    }
-
-    /** */
-    @Provides
-    public IActivityManager providesIActivityManager() {
-        return ActivityManager.getService();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/ServiceBinder.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/ServiceBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/ServiceBinder.java
index c11236e..1f2c0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ServiceBinder.java
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
 import android.app.Service;
 
+import com.android.systemui.ImageWallpaper;
 import com.android.systemui.doze.DozeService;
 import com.android.systemui.keyguard.KeyguardService;
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
new file mode 100644
index 0000000..fffba8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.app.IWallpaperManager;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.view.IWindowManager;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.MainResources;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
+ */
+@Module
+public class SystemServicesModule {
+
+    @Singleton
+    @Provides
+    static AlarmManager provideAlarmManager(Context context) {
+        return context.getSystemService(AlarmManager.class);
+    }
+
+    @Singleton
+    @Provides
+    static IActivityManager provideIActivityManager() {
+        return ActivityManager.getService();
+    }
+
+    @Provides
+    @Nullable
+    static IWallpaperManager provideIWallPaperManager() {
+        return IWallpaperManager.Stub.asInterface(
+                ServiceManager.getService(Context.WALLPAPER_SERVICE));
+    }
+
+    @Singleton
+    @Provides
+    static IWindowManager provideIWindowManager() {
+        return WindowManagerGlobal.getWindowManagerService();
+    }
+
+    @Singleton
+    @Provides
+    static LatencyTracker provideLatencyTracker(Context context) {
+        return LatencyTracker.getInstance(context);
+    }
+
+    @SuppressLint("MissingPermission")
+    @Singleton
+    @Provides
+    @Nullable
+    static LocalBluetoothManager provideLocalBluetoothController(Context context,
+            @BgHandler Handler bgHandler) {
+        return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
+    }
+
+    @Singleton
+    @Provides
+    static PackageManagerWrapper providePackageManagerWrapper() {
+        return PackageManagerWrapper.getInstance();
+    }
+
+    /** */
+    @Singleton
+    @Provides
+    static PowerManager providePowerManager(Context context) {
+        return context.getSystemService(PowerManager.class);
+    }
+
+    @Provides
+    @MainResources
+    static Resources provideResources(Context context) {
+        return context.getResources();
+    }
+
+    @Singleton
+    @Provides
+    static SensorPrivacyManager provideSensorPrivacyManager(Context context) {
+        return context.getSystemService(SensorPrivacyManager.class);
+    }
+
+    @Provides
+    static WallpaperManager provideWallpaperManager(Context context) {
+        return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
+    }
+
+    @Singleton
+    @Provides
+    static WindowManager provideWindowManager(Context context) {
+        return context.getSystemService(WindowManager.class);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index a5a5598..49cd414 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
+import com.android.systemui.SystemUI;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.pip.PipUI;
 import com.android.systemui.power.PowerUI;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 176bcbf..c95b50b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME;
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.SystemUI;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.power.EnhancedEstimates;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/SystemUIModule.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4520a1a6..41ac69c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -36,7 +36,9 @@
  * A dagger module for injecting components of System UI that are not overridden by the System UI
  * implementation.
  */
-@Module(includes = {AssistModule.class, ComponentBinder.class, PeopleHubModule.class})
+@Module(includes = {AssistModule.class,
+                    ComponentBinder.class,
+                    PeopleHubModule.class})
 public abstract class SystemUIModule {
 
     @Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
rename to packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
index bcbe672..113c9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
@@ -14,12 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger;
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 
 import android.content.ContentProvider;
 
+import com.android.systemui.Dependency;
+import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.InjectionInflationController;
@@ -36,6 +39,7 @@
 @Component(modules = {
         DependencyProvider.class,
         DependencyBinder.class,
+        SystemServicesModule.class,
         SystemUIFactory.ContextHolder.class,
         SystemUIModule.class,
         SystemUIDefaultModule.class})
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgHandler.java
similarity index 61%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgHandler.java
index d2ec357..bc6b83b 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,17 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.systemui.dagger.qualifiers;
 
-parcelable TimeSignal;
\ No newline at end of file
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface BgHandler {
+}
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgLooper.java
similarity index 61%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgLooper.java
index d2ec357..2aadda1 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/BgLooper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,17 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.systemui.dagger.qualifiers;
 
-parcelable TimeSignal;
\ No newline at end of file
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface BgLooper {
+}
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainHandler.java
similarity index 61%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainHandler.java
index d2ec357..79661fa 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainHandler.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,17 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.systemui.dagger.qualifiers;
 
-parcelable TimeSignal;
\ No newline at end of file
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MainHandler {
+}
diff --git a/core/java/android/app/timedetector/TimeSignal.aidl b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainLooper.java
similarity index 61%
copy from core/java/android/app/timedetector/TimeSignal.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainLooper.java
index d2ec357..750d7d7 100644
--- a/core/java/android/app/timedetector/TimeSignal.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainLooper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,17 @@
  * limitations under the License.
  */
 
-package android.app.timedetector;
+package com.android.systemui.dagger.qualifiers;
 
-parcelable TimeSignal;
\ No newline at end of file
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MainLooper {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainResources.java
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/ComponentBinder.java
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainResources.java
index 3b35c61..3daeda5 100644
--- a/packages/SystemUI/src/com/android/systemui/ComponentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/MainResources.java
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui;
+package com.android.systemui.dagger.qualifiers;
 
-import dagger.Binds;
-import dagger.Module;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-/**
- * Dagger Module that collects related sub-modules together.
- */
-@Module(includes = {ActivityBinder.class, ServiceBinder.class, SystemUIBinder.class})
-public abstract class ComponentBinder {
-    /** */
-    @Binds
-    public abstract ContextComponentHelper bindComponentHelper(
-            ContextComponentResolver componentHelper);
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface MainResources {
+    // TODO: use attribute to get other, non-main resources?
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index a36ff0d..33f68cf 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -57,6 +57,7 @@
     private final ProximitySensor mProximitySensor;
     private final DelayedWakeLock.Builder mDelayedWakeLockBuilder;
     private final Handler mHandler;
+    private final BiometricUnlockController mBiometricUnlockController;
 
     @Inject
     public DozeFactory(FalsingManager falsingManager, DozeLog dozeLog,
@@ -65,7 +66,8 @@
             WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor,
             DockManager dockManager, @Nullable IWallpaperManager wallpaperManager,
             ProximitySensor proximitySensor,
-            DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler) {
+            DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
+            BiometricUnlockController biometricUnlockController) {
         mFalsingManager = falsingManager;
         mDozeLog = dozeLog;
         mDozeParameters = dozeParameters;
@@ -79,6 +81,7 @@
         mProximitySensor = proximitySensor;
         mDelayedWakeLockBuilder = delayedWakeLockBuilder;
         mHandler = handler;
+        mBiometricUnlockController = biometricUnlockController;
     }
 
     /** Creates a DozeMachine with its parts for {@code dozeService}. */
@@ -107,9 +110,7 @@
                 createDozeScreenBrightness(dozeService, wrappedService, mAsyncSensorManager, host,
                         mDozeParameters, mHandler),
                 new DozeWallpaperState(
-                        mWallpaperManager,
-                        getBiometricUnlockController(dozeService),
-                        mDozeParameters),
+                        mWallpaperManager, mBiometricUnlockController, mDozeParameters),
                 new DozeDockHandler(dozeService, machine, host, config, mHandler, mDockManager),
                 new DozeAuthRemover(dozeService)
         });
@@ -149,10 +150,4 @@
         final SystemUIApplication app = (SystemUIApplication) appCandidate;
         return app.getComponent(DozeHost.class);
     }
-
-    public static BiometricUnlockController getBiometricUnlockController(DozeService service) {
-        Application appCandidate = service.getApplication();
-        final SystemUIApplication app = (SystemUIApplication) appCandidate;
-        return app.getComponent(BiometricUnlockController.class);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 1a6bd60..d1047e2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -68,6 +68,12 @@
      */
     void prepareForGentleSleep(Runnable onDisplayOffCallback);
 
+    /**
+     * Cancel pending {@code onDisplayOffCallback} callback.
+     * @see #prepareForGentleSleep(Runnable)
+     */
+    void cancelGentleSleep();
+
     void onIgnoreTouchWhilePulsing(boolean ignore);
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 95c42fc..e1b4f31 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -71,7 +71,7 @@
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         int screenState = newState.screenState(mParameters);
-        mDozeHost.prepareForGentleSleep(null);
+        mDozeHost.cancelGentleSleep();
 
         if (newState == DozeMachine.State.FINISH) {
             // Make sure not to apply the screen state after DozeService was destroyed.
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
index b4cc571..1b4857e 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentService.java
@@ -22,7 +22,7 @@
 
 import com.android.systemui.ConfigurationChangedReceiver;
 import com.android.systemui.Dumpable;
-import com.android.systemui.SystemUIRootComponent;
+import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.statusbar.phone.NavigationBarFragment;
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 5723afd..c452f64 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -90,7 +90,7 @@
     public static final int MESSAGE_UPDATE_ACTIONS = 4;
     public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5;
     public static final int MESSAGE_ANIMATION_ENDED = 6;
-    public static final int MESSAGE_TOUCH_EVENT = 7;
+    public static final int MESSAGE_POINTER_EVENT = 7;
 
     private static final int INITIAL_DISMISS_DELAY = 3500;
     private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
@@ -165,9 +165,9 @@
                     break;
                 }
 
-                case MESSAGE_TOUCH_EVENT: {
+                case MESSAGE_POINTER_EVENT: {
                     final MotionEvent ev = (MotionEvent) msg.obj;
-                    dispatchTouchEvent(ev);
+                    dispatchPointerEvent(ev);
                     break;
                 }
             }
@@ -219,6 +219,9 @@
         updateFromIntent(getIntent());
         setTitle(R.string.pip_menu_title);
         setDisablePreviewScreenshots(true);
+
+        // Hide without an animation.
+        getWindow().setExitTransition(null);
     }
 
     @Override
@@ -269,6 +272,17 @@
         }
     }
 
+    /**
+     * Dispatch a pointer event from {@link PipTouchHandler}.
+     */
+    private void dispatchPointerEvent(MotionEvent event) {
+        if (event.isTouchEvent()) {
+            dispatchTouchEvent(event);
+        } else {
+            dispatchGenericMotionEvent(event);
+        }
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (!mAllowTouches) {
@@ -288,8 +302,6 @@
     public void finish() {
         notifyActivityCallback(null);
         super.finish();
-        // Hide without an animation (the menu should already be invisible at this point)
-        overridePendingTransition(0, 0);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 62c59e5..b8e0b81 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -508,12 +508,12 @@
     }
 
     /**
-     * Handles touch event sent from pip input consumer.
+     * Handles a pointer event sent from pip input consumer.
      */
-    void handleTouchEvent(MotionEvent ev) {
+    void handlePointerEvent(MotionEvent ev) {
         if (mToActivityMessenger != null) {
             Message m = Message.obtain();
-            m.what = PipMenuActivity.MESSAGE_TOUCH_EVENT;
+            m.what = PipMenuActivity.MESSAGE_POINTER_EVENT;
             m.obj = ev;
             try {
                 mToActivityMessenger.send(m);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 1f36d97..f59b372 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -132,6 +132,7 @@
     private boolean mSendingHoverAccessibilityEvents;
     private boolean mMovementWithinMinimize;
     private boolean mMovementWithinDismiss;
+    private PipAccessibilityInteractionConnection mConnection;
 
     // Touch state
     private final PipTouchState mTouchState;
@@ -213,9 +214,10 @@
         // Register the listener for input consumer touch events
         inputConsumerController.setInputListener(this::handleTouchEvent);
         inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
-        onRegistrationChanged(inputConsumerController.isRegistered());
 
         mPipBoundsHandler = pipBoundsHandler;
+        mConnection = new PipAccessibilityInteractionConnection(mMotionHelper,
+                this::onAccessibilityShowMenu, mHandler);
     }
 
     public void setTouchEnabled(boolean enabled) {
@@ -339,9 +341,7 @@
 
     private void onRegistrationChanged(boolean isRegistered) {
         mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
-                ? new PipAccessibilityInteractionConnection(mMotionHelper,
-                        this::onAccessibilityShowMenu, mHandler) : null);
-
+                ? mConnection : null);
         if (!isRegistered && mTouchState.isUserInteracting()) {
             // If the input consumer is unregistered while the user is interacting, then we may not
             // get the final TOUCH_UP event, so clean up the dismiss target as well
@@ -409,27 +409,15 @@
             }
             case MotionEvent.ACTION_HOVER_ENTER:
             case MotionEvent.ACTION_HOVER_MOVE: {
-                if (mAccessibilityManager.isEnabled() && !mSendingHoverAccessibilityEvents) {
-                    AccessibilityEvent event = AccessibilityEvent.obtain(
-                            AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
-                    event.setImportantForAccessibility(true);
-                    event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
-                    event.setWindowId(
-                            AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
-                    mAccessibilityManager.sendAccessibilityEvent(event);
+                if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) {
+                    sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
                     mSendingHoverAccessibilityEvents = true;
                 }
                 break;
             }
             case MotionEvent.ACTION_HOVER_EXIT: {
-                if (mAccessibilityManager.isEnabled() && mSendingHoverAccessibilityEvents) {
-                    AccessibilityEvent event = AccessibilityEvent.obtain(
-                            AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
-                    event.setImportantForAccessibility(true);
-                    event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
-                    event.setWindowId(
-                            AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
-                    mAccessibilityManager.sendAccessibilityEvent(event);
+                if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) {
+                    sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
                     mSendingHoverAccessibilityEvents = false;
                 }
                 break;
@@ -445,12 +433,25 @@
                 mMenuController.pokeMenu();
             }
 
-            mMenuController.handleTouchEvent(cloneEvent);
+            mMenuController.handlePointerEvent(cloneEvent);
         }
 
         return true;
     }
 
+    private void sendAccessibilityHoverEvent(int type) {
+        if (!mAccessibilityManager.isEnabled()) {
+            return;
+        }
+
+        AccessibilityEvent event = AccessibilityEvent.obtain(type);
+        event.setImportantForAccessibility(true);
+        event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
+        event.setWindowId(
+                AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
+        mAccessibilityManager.sendAccessibilityEvent(event);
+    }
+
     /**
      * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
      */
@@ -523,6 +524,10 @@
      * Sets the menu visibility.
      */
     private void setMenuState(int menuState, boolean resize) {
+        if (mMenuState == menuState && !resize) {
+            return;
+        }
+
         if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) {
             // Save the current snap fraction and if we do not drag or move the PiP, then
             // we store back to this snap fraction.  Otherwise, we'll reset the snap
@@ -571,6 +576,9 @@
         }
         mMenuState = menuState;
         updateMovementBounds(menuState);
+        // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
+        // as well, or it can't handle a11y focus and pip menu can't perform any action.
+        onRegistrationChanged(menuState == MENU_STATE_NONE);
         if (menuState != MENU_STATE_CLOSE) {
             MetricsLoggerWrapper.logPictureInPictureMenuVisible(mContext, menuState == MENU_STATE_FULL);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 631b8b7..22fb4c0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -22,25 +22,20 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
-import android.os.Handler
-import android.os.Looper
-import android.os.Message
-import android.os.UserHandle
-import android.os.UserManager
+import android.os.*
 import android.provider.DeviceConfig
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
-import com.android.systemui.Dependency.BG_HANDLER_NAME
-import com.android.systemui.Dependency.MAIN_HANDLER_NAME
+import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.appops.AppOpItem
 import com.android.systemui.appops.AppOpsController
-import com.android.systemui.Dumpable
+import com.android.systemui.dagger.qualifiers.BgHandler
+import com.android.systemui.dagger.qualifiers.MainHandler
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.lang.ref.WeakReference
 import javax.inject.Inject
-import javax.inject.Named
 import javax.inject.Singleton
 
 fun isPermissionsHubEnabled() = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
@@ -50,8 +45,8 @@
 class PrivacyItemController @Inject constructor(
     val context: Context,
     private val appOpsController: AppOpsController,
-    @Named(MAIN_HANDLER_NAME) private val uiHandler: Handler,
-    @Named(BG_HANDLER_NAME) private val bgHandler: Handler
+    @MainHandler private val uiHandler: Handler,
+    @BgHandler private val bgHandler: Handler
 ) : Dumpable {
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
new file mode 100644
index 0000000..f710f7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.qs
+
+import android.content.Context
+import android.content.res.Configuration
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.R
+import com.android.systemui.qs.TileLayout.exactly
+
+class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTileLayout {
+
+    protected val mRecords = ArrayList<QSPanel.TileRecord>()
+    private var _listening = false
+    private var smallTileSize = 0
+    private val twoLineHeight
+        get() = smallTileSize * 2 + cellMarginVertical
+    private var cellMarginHorizontal = 0
+    private var cellMarginVertical = 0
+
+    init {
+        isFocusableInTouchMode = true
+        clipChildren = false
+        clipToPadding = false
+
+        updateResources()
+    }
+
+    override fun addTile(tile: QSPanel.TileRecord) {
+        mRecords.add(tile)
+        tile.tile.setListening(this, _listening)
+        addTileView(tile)
+    }
+
+    protected fun addTileView(tile: QSPanel.TileRecord) {
+        addView(tile.tileView)
+    }
+
+    override fun removeTile(tile: QSPanel.TileRecord) {
+        mRecords.remove(tile)
+        tile.tile.setListening(this, false)
+        removeView(tile.tileView)
+    }
+
+    override fun removeAllViews() {
+        mRecords.forEach { it.tile.setListening(this, false) }
+        mRecords.clear()
+        super.removeAllViews()
+    }
+
+    override fun getOffsetTop(tile: QSPanel.TileRecord?) = top
+
+    override fun updateResources(): Boolean {
+        with(mContext.resources) {
+            smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size)
+            cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal)
+            cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin)
+        }
+        requestLayout()
+        return false
+    }
+
+    override fun setListening(listening: Boolean) {
+        if (_listening == listening) return
+        _listening = listening
+        for (record in mRecords) {
+            record.tile.setListening(this, listening)
+        }
+    }
+
+    override fun getNumVisibleTiles() = mRecords.size
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        updateResources()
+    }
+
+    override fun onFinishInflate() {
+        updateResources()
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        var previousView: View = this
+        var tiles = 0
+
+        mRecords.forEach {
+            val tileView = it.tileView
+            if (tileView.visibility != View.GONE) {
+                tileView.updateAccessibilityOrder(previousView)
+                previousView = tileView
+                tiles++
+                tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
+            }
+        }
+
+        val height = twoLineHeight
+        val columns = tiles / 2
+        val width = paddingStart + paddingEnd +
+                columns * smallTileSize +
+                (columns - 1) * cellMarginHorizontal
+        setMeasuredDimension(width, height)
+    }
+
+    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+        val tiles = mRecords.filter { it.tileView.visibility != View.GONE }
+        tiles.forEachIndexed {
+            index, tile ->
+            val column = index % (tiles.size / 2)
+            val left = getLeftForColumn(column)
+            val top = if (index < tiles.size / 2) 0 else getTopBottomRow()
+            tile.tileView.layout(left, top, left + smallTileSize, top + smallTileSize)
+        }
+    }
+
+    private fun getLeftForColumn(column: Int) = column * (smallTileSize + cellMarginHorizontal)
+
+    private fun getTopBottomRow() = smallTileSize + cellMarginVertical
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 9221b68..a267bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui.qs;
 
+import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
@@ -267,6 +268,17 @@
             mAllViews.add(tileView);
             count++;
         }
+
+
+        int flag = Settings.System.getInt(mQsPanel.getContext().getContentResolver(),
+                "qs_media_player", 0);
+        if (flag == 1) {
+            View qsMediaView = mQsPanel.getMediaPanel();
+            View qqsMediaView = mQuickQsPanel.getMediaPlayer().getView();
+            translationXBuilder.addFloat(qsMediaView, "alpha", 0, 1);
+            translationXBuilder.addFloat(qqsMediaView, "alpha", 1, 0);
+        }
+
         if (mAllowFancy) {
             // Make brightness appear static position and alpha in through second half.
             View brightness = mQsPanel.getBrightnessView();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
index b9f3a7f..5742787 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
@@ -17,9 +17,7 @@
 package com.android.systemui.qs;
 
 import static com.android.systemui.Dependency.BG_HANDLER;
-import static com.android.systemui.Dependency.BG_HANDLER_NAME;
 import static com.android.systemui.Dependency.MAIN_LOOPER;
-import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
 import android.annotation.MainThread;
@@ -41,6 +39,8 @@
 import com.android.keyguard.CarrierTextController;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.BgHandler;
+import com.android.systemui.dagger.qualifiers.MainLooper;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.NetworkController;
 
@@ -77,8 +77,8 @@
     @Inject
     public QSCarrierGroup(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             NetworkController networkController, ActivityStarter activityStarter,
-            @Named(BG_HANDLER_NAME) Handler handler,
-            @Named(MAIN_LOOPER_NAME) Looper looper) {
+            @BgHandler Handler handler,
+            @MainLooper Looper looper) {
         super(context, attrs);
         mNetworkController = networkController;
         mActivityStarter = activityStarter;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
new file mode 100644
index 0000000..af418f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -0,0 +1,301 @@
+/*
+ * 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.qs;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.RippleDrawable;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.settingslib.media.MediaOutputSliceConstants;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.MediaTransferManager;
+
+/**
+ * Single media player for carousel in QSPanel
+ */
+public class QSMediaPlayer {
+
+    private static final String TAG = "QSMediaPlayer";
+
+    private Context mContext;
+    private LinearLayout mMediaNotifView;
+    private MediaSession.Token mToken;
+    private MediaController mController;
+    private int mWidth;
+    private int mHeight;
+
+    /**
+     *
+     * @param context
+     * @param parent
+     * @param width
+     * @param height
+     */
+    public QSMediaPlayer(Context context, ViewGroup parent, int width, int height) {
+        mContext = context;
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qs_media_panel, parent, false);
+
+        mWidth = width;
+        mHeight = height;
+    }
+
+    public View getView() {
+        return mMediaNotifView;
+    }
+
+    /**
+     *
+     * @param token token for this media session
+     * @param icon app notification icon
+     * @param iconColor foreground color (for text, icons)
+     * @param bgColor background color
+     * @param actionsContainer a LinearLayout containing the media action buttons
+     * @param notif
+     */
+    public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
+            View actionsContainer, Notification notif) {
+        Log.d(TAG, "got media session: " + token);
+        mToken = token;
+        mController = new MediaController(mContext, token);
+        MediaMetadata mMediaMetadata = mController.getMetadata();
+        Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif);
+
+        // Album art
+        addAlbumArtBackground(mMediaMetadata, bgColor, mWidth, mHeight);
+
+        // Reuse notification header instead of reimplementing everything
+        RemoteViews headerRemoteView = builder.makeNotificationHeader();
+        LinearLayout headerView = mMediaNotifView.findViewById(R.id.header);
+        View result = headerRemoteView.apply(mContext, headerView);
+        result.setPadding(0, 0, 0, 0);
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT, 75);
+        result.setLayoutParams(lp);
+        headerView.removeAllViews();
+        headerView.addView(result);
+
+        View seamless = headerView.findViewById(com.android.internal.R.id.media_seamless);
+        seamless.setVisibility(View.VISIBLE);
+
+        // App icon
+        ImageView appIcon = headerView.findViewById(com.android.internal.R.id.icon);
+        Drawable iconDrawable = icon.loadDrawable(mContext);
+        iconDrawable.setTint(iconColor);
+        appIcon.setImageDrawable(iconDrawable);
+
+        // App title
+        TextView appName = headerView.findViewById(com.android.internal.R.id.app_name_text);
+        String appNameString = builder.loadHeaderAppName();
+        appName.setText(appNameString);
+        appName.setTextColor(iconColor);
+
+        // Action
+        mMediaNotifView.setOnClickListener(v -> {
+            try {
+                notif.contentIntent.send();
+                // Also close shade
+                mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(TAG, "Pending intent was canceled");
+                e.printStackTrace();
+            }
+        });
+
+        // Separator
+        TextView separator = headerView.findViewById(com.android.internal.R.id.header_text_divider);
+        separator.setTextColor(iconColor);
+
+        // Album name
+        TextView albumName = headerView.findViewById(com.android.internal.R.id.header_text);
+        String albumString = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
+        if (!albumString.isEmpty()) {
+            albumName.setText(albumString);
+            albumName.setTextColor(iconColor);
+            albumName.setVisibility(View.VISIBLE);
+            separator.setVisibility(View.VISIBLE);
+        } else {
+            albumName.setVisibility(View.GONE);
+            separator.setVisibility(View.GONE);
+        }
+
+        // Transfer chip
+        MediaTransferManager mediaTransferManager = new MediaTransferManager(mContext);
+        View transferBackgroundView = headerView.findViewById(
+                com.android.internal.R.id.media_seamless);
+        LinearLayout viewLayout = (LinearLayout) transferBackgroundView;
+        RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
+        GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
+        rect.setStroke(2, iconColor);
+        rect.setColor(bgColor);
+        ImageView transferIcon = headerView.findViewById(
+                com.android.internal.R.id.media_seamless_image);
+        transferIcon.setBackgroundColor(bgColor);
+        transferIcon.setImageTintList(ColorStateList.valueOf(iconColor));
+        TextView transferText = headerView.findViewById(
+                com.android.internal.R.id.media_seamless_text);
+        transferText.setTextColor(iconColor);
+
+        ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
+        transferBackgroundView.setOnClickListener(v -> {
+            final Intent intent = new Intent()
+                    .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT);
+            mActivityStarter.startActivity(intent, false, true /* dismissShade */,
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        });
+
+        // Artist name
+        TextView artistText = mMediaNotifView.findViewById(R.id.header_title);
+        String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+        artistText.setText(artistName);
+        artistText.setTextColor(iconColor);
+
+        // Song name
+        TextView titleText = mMediaNotifView.findViewById(R.id.header_text);
+        String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+        titleText.setText(songName);
+        titleText.setTextColor(iconColor);
+
+        // Media controls
+        LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
+        final int[] actionIds = {
+                R.id.action0,
+                R.id.action1,
+                R.id.action2,
+                R.id.action3,
+                R.id.action4
+        };
+        final int[] notifActionIds = {
+                com.android.internal.R.id.action0,
+                com.android.internal.R.id.action1,
+                com.android.internal.R.id.action2,
+                com.android.internal.R.id.action3,
+                com.android.internal.R.id.action4
+        };
+        for (int i = 0; i < parentActionsLayout.getChildCount() && i < actionIds.length; i++) {
+            ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
+            ImageButton thatBtn = parentActionsLayout.findViewById(notifActionIds[i]);
+            if (thatBtn == null || thatBtn.getDrawable() == null) {
+                thisBtn.setVisibility(View.GONE);
+                continue;
+            }
+
+            Drawable thatIcon = thatBtn.getDrawable();
+            thisBtn.setImageDrawable(thatIcon.mutate());
+            thisBtn.setVisibility(View.VISIBLE);
+            thisBtn.setOnClickListener(v -> {
+                Log.d(TAG, "clicking on other button");
+                thatBtn.performClick();
+            });
+        }
+    }
+
+    public MediaSession.Token getMediaSessionToken() {
+        return mToken;
+    }
+
+    public String getMediaPlayerPackage() {
+        return mController.getPackageName();
+    }
+
+    /**
+     * Check whether the media controlled by this player is currently playing
+     * @return whether it is playing, or false if no controller information
+     */
+    public boolean isPlaying() {
+        if (mController == null) {
+            return false;
+        }
+
+        PlaybackState state = mController.getPlaybackState();
+        if (state == null) {
+            return false;
+        }
+
+        return (state.getState() == PlaybackState.STATE_PLAYING);
+    }
+
+    private void addAlbumArtBackground(MediaMetadata metadata, int bgColor, int width, int height) {
+        Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        if (albumArt != null) {
+
+            Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
+            Bitmap scaled = scaleBitmap(original, width, height);
+            Canvas canvas = new Canvas(scaled);
+
+            // Add translucent layer over album art to improve contrast
+            Paint p = new Paint();
+            p.setStyle(Paint.Style.FILL);
+            p.setColor(bgColor);
+            p.setAlpha(200);
+            canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), p);
+
+            RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
+                    mContext.getResources(), scaled);
+            roundedDrawable.setCornerRadius(20);
+
+            mMediaNotifView.setBackground(roundedDrawable);
+        } else {
+            Log.e(TAG, "No album art available");
+        }
+    }
+
+    private Bitmap scaleBitmap(Bitmap original, int width, int height) {
+        Bitmap cropped = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(cropped);
+
+        float scale = (float) cropped.getWidth() / (float) original.getWidth();
+        float dy = (cropped.getHeight() - original.getHeight() * scale) / 2.0f;
+        Matrix transformation = new Matrix();
+        transformation.postTranslate(0, dy);
+        transformation.preScale(scale, scale);
+
+        Paint paint = new Paint();
+        paint.setFilterBitmap(true);
+        canvas.drawBitmap(original, transformation, paint);
+
+        return cropped;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 2e24403..2060059 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -24,16 +24,22 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.media.session.MediaSession;
 import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
 import android.service.quicksettings.Tile;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 
 import com.android.internal.logging.MetricsLogger;
@@ -82,6 +88,9 @@
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
     private final QSTileRevealController mQsTileRevealController;
 
+    private final LinearLayout mMediaCarousel;
+    private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>();
+
     protected boolean mExpanded;
     protected boolean mListening;
 
@@ -140,6 +149,27 @@
 
         addDivider();
 
+        // Add media carousel
+        int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_player", 0);
+        if (flag == 1) {
+            HorizontalScrollView mediaScrollView = new HorizontalScrollView(mContext);
+            mediaScrollView.setHorizontalScrollBarEnabled(false);
+            int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height);
+            int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
+            LayoutParams lpView = new LayoutParams(LayoutParams.MATCH_PARENT, playerHeight);
+            lpView.setMarginStart(padding);
+            lpView.setMarginEnd(padding);
+            addView(mediaScrollView, lpView);
+
+            LayoutParams lpCarousel = new LayoutParams(LayoutParams.MATCH_PARENT,
+                    LayoutParams.WRAP_CONTENT);
+            mMediaCarousel = new LinearLayout(mContext);
+            mMediaCarousel.setOrientation(LinearLayout.HORIZONTAL);
+            mediaScrollView.addView(mMediaCarousel, lpCarousel);
+        } else {
+            mMediaCarousel = null;
+        }
+
         mFooter = new QSSecurityFooter(this, context);
         addView(mFooter.getView());
 
@@ -159,6 +189,72 @@
 
     }
 
+    /**
+     * Add or update a player for the associated media session
+     * @param token
+     * @param icon
+     * @param iconColor
+     * @param bgColor
+     * @param actionsContainer
+     * @param notif
+     */
+    public void addMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
+            View actionsContainer, StatusBarNotification notif) {
+        int flag = Settings.System.getInt(mContext.getContentResolver(), "qs_media_player", 0);
+        if (flag != 1) {
+            // Shouldn't happen, but just in case
+            Log.e(TAG, "Tried to add media session without player!");
+            return;
+        }
+        QSMediaPlayer player = null;
+        String packageName = notif.getPackageName();
+        for (QSMediaPlayer p : mMediaPlayers) {
+            if (p.getMediaSessionToken().equals(token)) {
+                Log.d(TAG, "a player for this session already exists");
+                player = p;
+                break;
+            }
+
+            if (packageName.equals(p.getMediaPlayerPackage())) {
+                Log.d(TAG, "found an old session for this app");
+                player = p;
+                break;
+            }
+        }
+
+        int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height);
+        int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width);
+        int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
+        LayoutParams lp = new LayoutParams(playerWidth, ViewGroup.LayoutParams.MATCH_PARENT);
+        lp.setMarginStart(padding);
+        lp.setMarginEnd(padding);
+
+        if (player == null) {
+            Log.d(TAG, "creating new player");
+
+            player = new QSMediaPlayer(mContext, this, playerWidth, playerHeight);
+
+            if (player.isPlaying()) {
+                mMediaCarousel.addView(player.getView(), 0, lp); // add in front
+            } else {
+                mMediaCarousel.addView(player.getView(), lp); // add at end
+            }
+        } else if (player.isPlaying()) {
+            // move it to the front
+            mMediaCarousel.removeView(player.getView());
+            mMediaCarousel.addView(player.getView(), 0, lp);
+        }
+
+        Log.d(TAG, "setting player session");
+        player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
+                notif.getNotification());
+        mMediaPlayers.add(player);
+    }
+
+    protected View getMediaPanel() {
+        return mMediaCarousel;
+    }
+
     protected void addDivider() {
         mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
         mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 1e763cf..b395c3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -30,11 +30,12 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.DumpController;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
@@ -61,7 +62,6 @@
 import java.util.function.Predicate;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Provider;
 import javax.inject.Singleton;
 
@@ -94,8 +94,8 @@
     public QSTileHost(Context context,
             StatusBarIconController iconController,
             QSFactoryImpl defaultFactory,
-            @Named(Dependency.MAIN_HANDLER_NAME) Handler mainHandler,
-            @Named(Dependency.BG_LOOPER_NAME) Looper bgLooper,
+            @MainHandler Handler mainHandler,
+            @BgLooper Looper bgLooper,
             PluginManager pluginManager,
             TunerService tunerService,
             Provider<AutoTileManager> autoTiles,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
new file mode 100644
index 0000000..ae66cd5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -0,0 +1,203 @@
+/*
+ * 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.qs;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.systemui.R;
+
+/**
+ * QQS mini media player
+ */
+public class QuickQSMediaPlayer {
+
+    private static final String TAG = "QQSMediaPlayer";
+
+    private Context mContext;
+    private LinearLayout mMediaNotifView;
+    private MediaSession.Token mToken;
+    private MediaController mController;
+
+    /**
+     *
+     * @param context
+     * @param parent
+     */
+    public QuickQSMediaPlayer(Context context, ViewGroup parent) {
+        mContext = context;
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        mMediaNotifView = (LinearLayout) inflater.inflate(R.layout.qqs_media_panel, parent, false);
+    }
+
+    public View getView() {
+        return mMediaNotifView;
+    }
+
+    /**
+     *
+     * @param token token for this media session
+     * @param icon app notification icon
+     * @param iconColor foreground color (for text, icons)
+     * @param bgColor background color
+     * @param actionsContainer a LinearLayout containing the media action buttons
+     */
+    public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
+            View actionsContainer) {
+        Log.d(TAG, "Setting media session: " + token);
+        mToken = token;
+        mController = new MediaController(mContext, token);
+        MediaMetadata mMediaMetadata = mController.getMetadata();
+
+        // Album art
+        addAlbumArtBackground(mMediaMetadata, bgColor);
+
+        // App icon
+        ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
+        Drawable iconDrawable = icon.loadDrawable(mContext);
+        iconDrawable.setTint(iconColor);
+        appIcon.setImageDrawable(iconDrawable);
+
+        // Artist name
+        TextView appText = mMediaNotifView.findViewById(R.id.header_title);
+        String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+        appText.setText(artistName);
+        appText.setTextColor(iconColor);
+
+        // Song name
+        TextView titleText = mMediaNotifView.findViewById(R.id.header_text);
+        String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+        titleText.setText(songName);
+        titleText.setTextColor(iconColor);
+
+        // Action buttons
+        LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
+        final int[] actionIds = {R.id.action0, R.id.action1, R.id.action2};
+
+        // TODO some apps choose different buttons to show in compact mode
+        final int[] notifActionIds = {
+                com.android.internal.R.id.action1,
+                com.android.internal.R.id.action2,
+                com.android.internal.R.id.action3
+        };
+        for (int i = 0; i < parentActionsLayout.getChildCount() && i < actionIds.length; i++) {
+            ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
+            ImageButton thatBtn = parentActionsLayout.findViewById(notifActionIds[i]);
+            if (thatBtn == null || thatBtn.getDrawable() == null) {
+                thisBtn.setVisibility(View.GONE);
+                continue;
+            }
+
+            Drawable thatIcon = thatBtn.getDrawable();
+            thisBtn.setImageDrawable(thatIcon.mutate());
+            thisBtn.setVisibility(View.VISIBLE);
+
+            thisBtn.setOnClickListener(v -> {
+                Log.d(TAG, "clicking on other button");
+                thatBtn.performClick();
+            });
+        }
+    }
+
+    public MediaSession.Token getMediaSessionToken() {
+        return mToken;
+    }
+
+    /**
+     * Check whether the media controlled by this player is currently playing
+     * @return whether it is playing, or false if no controller information
+     */
+    public boolean isPlaying() {
+        if (mController == null) {
+            return false;
+        }
+
+        PlaybackState state = mController.getPlaybackState();
+        if (state == null) {
+            return false;
+        }
+
+        return (state.getState() == PlaybackState.STATE_PLAYING);
+    }
+
+    private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) {
+        Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        if (albumArt != null) {
+            Rect bounds = new Rect();
+            mMediaNotifView.getBoundsOnScreen(bounds);
+            int width = bounds.width();
+            int height = bounds.height();
+
+            Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
+            Bitmap scaled = scaleBitmap(original, width, height);
+            Canvas canvas = new Canvas(scaled);
+
+            // Add translucent layer over album art to improve contrast
+            Paint p = new Paint();
+            p.setStyle(Paint.Style.FILL);
+            p.setColor(bgColor);
+            p.setAlpha(200);
+            canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), p);
+
+            RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
+                    mContext.getResources(), scaled);
+            roundedDrawable.setCornerRadius(20);
+
+            mMediaNotifView.setBackground(roundedDrawable);
+        } else {
+            Log.e(TAG, "No album art available");
+        }
+    }
+
+    private Bitmap scaleBitmap(Bitmap original, int width, int height) {
+        Bitmap cropped = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(cropped);
+
+        float scale = (float) cropped.getWidth() / (float) original.getWidth();
+        float dy = (cropped.getHeight() - original.getHeight() * scale) / 2.0f;
+        Matrix transformation = new Matrix();
+        transformation.postTranslate(0, dy);
+        transformation.preScale(scale, scale);
+
+        Paint paint = new Paint();
+        paint.setFilterBitmap(true);
+        canvas.drawBitmap(original, transformation, paint);
+
+        return cropped;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 85aafa0..dcd4633 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
@@ -55,6 +56,7 @@
     private boolean mDisabledByPolicy;
     private int mMaxTiles;
     protected QSPanel mFullPanel;
+    private QuickQSMediaPlayer mMediaPlayer;
 
     @Inject
     public QuickQSPanel(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
@@ -69,11 +71,43 @@
             }
             removeView((View) mTileLayout);
         }
-        sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
-        mTileLayout = new HeaderTileLayout(context);
-        mTileLayout.setListening(mListening);
-        addView((View) mTileLayout, 0 /* Between brightness and footer */);
-        super.setPadding(0, 0, 0, 0);
+
+        int flag = Settings.System.getInt(context.getContentResolver(), "qs_media_player", 0);
+        if (flag == 1) {
+            LinearLayout mHorizontalLinearLayout = new LinearLayout(mContext);
+            mHorizontalLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
+            mHorizontalLinearLayout.setClipChildren(false);
+            mHorizontalLinearLayout.setClipToPadding(false);
+
+            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
+
+            mTileLayout = new DoubleLineTileLayout(context);
+            lp.setMarginEnd(10);
+            lp.setMarginStart(0);
+            mHorizontalLinearLayout.addView((View) mTileLayout, lp);
+
+            mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout);
+
+            lp.setMarginEnd(0);
+            lp.setMarginStart(10);
+            mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp);
+
+            sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
+
+            mTileLayout.setListening(mListening);
+            addView(mHorizontalLinearLayout, 0 /* Between brightness and footer */);
+            super.setPadding(0, 0, 0, 0);
+        } else {
+            sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
+            mTileLayout = new HeaderTileLayout(context);
+            mTileLayout.setListening(mListening);
+            addView((View) mTileLayout, 0 /* Between brightness and footer */);
+            super.setPadding(0, 0, 0, 0);
+        }
+    }
+
+    public QuickQSMediaPlayer getMediaPlayer() {
+        return mMediaPlayer;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 4013586..592e388 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -392,9 +392,15 @@
         mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams());
 
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+
+        int flag = Settings.System.getInt(mContext.getContentResolver(), "qs_media_player", 0);
         if (mQsDisabled) {
             lp.height = resources.getDimensionPixelSize(
                     com.android.internal.R.dimen.quick_qs_offset_height);
+        } else if (flag == 1) {
+            lp.height = Math.max(getMinimumHeight(),
+                    resources.getDimensionPixelSize(
+                            com.android.internal.R.dimen.quick_qs_total_height_with_media));
         } else {
             lp.height = Math.max(getMinimumHeight(),
                     resources.getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c9dc08e..34f5437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -261,9 +261,9 @@
         default void showAuthenticationDialog(Bundle bundle,
                 IBiometricServiceReceiverInternal receiver, int biometricModality,
                 boolean requireConfirmation, int userId, String opPackageName) { }
-        default void onBiometricAuthenticated(boolean authenticated, String failureReason) { }
+        default void onBiometricAuthenticated() { }
         default void onBiometricHelp(String message) { }
-        default void onBiometricError(int errorCode, String error) { }
+        default void onBiometricError(int modality, int error, int vendorCode) { }
         default void hideAuthenticationDialog() { }
 
         /**
@@ -792,12 +792,9 @@
     }
 
     @Override
-    public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
+    public void onBiometricAuthenticated() {
         synchronized (mLock) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = authenticated;
-            args.arg2 = failureReason;
-            mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget();
+            mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget();
         }
     }
 
@@ -809,9 +806,13 @@
     }
 
     @Override
-    public void onBiometricError(int errorCode, String error) {
+    public void onBiometricError(int modality, int error, int vendorCode) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, errorCode, 0, error).sendToTarget();
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = modality;
+            args.argi2 = error;
+            args.argi3 = vendorCode;
+            mHandler.obtainMessage(MSG_BIOMETRIC_ERROR, args).sendToTarget();
         }
     }
 
@@ -1098,13 +1099,9 @@
                     break;
                 }
                 case MSG_BIOMETRIC_AUTHENTICATED: {
-                    SomeArgs someArgs = (SomeArgs) msg.obj;
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).onBiometricAuthenticated(
-                                (boolean) someArgs.arg1 /* authenticated */,
-                                (String) someArgs.arg2 /* failureReason */);
+                        mCallbacks.get(i).onBiometricAuthenticated();
                     }
-                    someArgs.recycle();
                     break;
                 }
                 case MSG_BIOMETRIC_HELP:
@@ -1113,9 +1110,15 @@
                     }
                     break;
                 case MSG_BIOMETRIC_ERROR:
+                    SomeArgs someArgs = (SomeArgs) msg.obj;
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).onBiometricError(msg.arg1, (String) msg.obj);
+                        mCallbacks.get(i).onBiometricError(
+                                someArgs.argi1 /* modality */,
+                                someArgs.argi2 /* error */,
+                                someArgs.argi3 /* vendorCode */
+                        );
                     }
+                    someArgs.recycle();
                     break;
                 case MSG_BIOMETRIC_HIDE:
                     for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 0679595..341c49a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -16,18 +16,17 @@
 
 package com.android.systemui.statusbar;
 
-import static com.android.systemui.Dependency.BG_HANDLER_NAME;
-
 import android.annotation.NonNull;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 
+import com.android.systemui.dagger.qualifiers.BgHandler;
+
 import java.util.Map;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -50,8 +49,7 @@
     private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>();
 
     @Inject
-    public FeatureFlags(
-            @Named(BG_HANDLER_NAME) Handler bgHandler) {
+    public FeatureFlags(@BgHandler Handler bgHandler) {
         DeviceConfig.addOnPropertiesChangedListener(
                 "systemui",
                 new HandlerExecutor(bgHandler),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 275475d6..1f38904 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -18,7 +18,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
 import static com.android.systemui.SysUiServiceProvider.getComponent;
 
 import android.content.Context;
@@ -38,6 +37,7 @@
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistHandleViewController;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -48,7 +48,6 @@
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 
@@ -67,7 +66,7 @@
     SparseArray<NavigationBarFragment> mNavigationBars = new SparseArray<>();
 
     @Inject
-    public NavigationBarController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler) {
+    public NavigationBarController(Context context, @MainHandler Handler handler) {
         mContext = context;
         mHandler = handler;
         mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 23968d5..c838ac5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -17,8 +17,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -52,6 +50,7 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -69,7 +68,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 import dagger.Lazy;
@@ -262,7 +260,7 @@
             NotificationEntryManager notificationEntryManager,
             Lazy<ShadeController> shadeController,
             StatusBarStateController statusBarStateController,
-            @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+            @MainHandler Handler mainHandler) {
         mContext = context;
         mLockscreenUserManager = lockscreenUserManager;
         mSmartReplyController = smartReplyController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index c2bb5b7..d6b87af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -16,19 +16,19 @@
 
 package com.android.systemui.statusbar;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -47,7 +47,6 @@
 import java.util.Stack;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 import dagger.Lazy;
@@ -88,6 +87,7 @@
     private final BubbleController mBubbleController;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final KeyguardBypassController mBypassController;
+    private final Context mContext;
 
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
@@ -99,8 +99,7 @@
     private boolean mIsHandleDynamicPrivacyChangeScheduled;
 
     @Inject
-    public NotificationViewHierarchyManager(Context context,
-            @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+    public NotificationViewHierarchyManager(Context context, @MainHandler Handler mainHandler,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             NotificationGroupManager groupManager,
             VisualStabilityManager visualStabilityManager,
@@ -110,6 +109,7 @@
             KeyguardBypassController bypassController,
             BubbleController bubbleController,
             DynamicPrivacyController privacyController) {
+        mContext = context;
         mHandler = mainHandler;
         mLockscreenUserManager = notificationLockscreenUserManager;
         mBypassController = bypassController;
@@ -146,7 +146,11 @@
         final int N = activeNotifications.size();
         for (int i = 0; i < N; i++) {
             NotificationEntry ent = activeNotifications.get(i);
+            int flag = Settings.System.getInt(mContext.getContentResolver(),
+                    "qs_media_player", 0);
+            boolean hideMedia = (flag == 1);
             if (ent.isRowDismissed() || ent.isRowRemoved()
+                    || (ent.isMediaNotification() && hideMedia)
                     || mBubbleController.isBubbleNotificationSuppressedFromShade(ent.getKey())) {
                 // we don't want to update removed notifications because they could
                 // temporarily become children if they were isolated before.
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 6e464f4..b4dc538 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -138,6 +138,14 @@
         mNotificationEntryListeners.add(listener);
     }
 
+    /**
+     * Removes a {@link NotificationEntryListener} previously registered via
+     * {@link #addNotificationEntryListener(NotificationEntryListener)}.
+     */
+    public void removeNotificationEntryListener(NotificationEntryListener listener) {
+        mNotificationEntryListeners.remove(listener);
+    }
+
     /** Sets the {@link NotificationRemoveInterceptor}. */
     public void setNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
         mRemoveInterceptor = interceptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 6fe4abe..1daf484 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.os.Handler;
 import android.os.SystemClock;
 import android.view.View;
@@ -25,6 +23,7 @@
 import androidx.collection.ArraySet;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -35,7 +34,6 @@
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -64,8 +62,7 @@
 
     @Inject
     public VisualStabilityManager(
-            NotificationEntryManager notificationEntryManager,
-            @Named(MAIN_HANDLER_NAME) Handler handler) {
+            NotificationEntryManager notificationEntryManager, @MainHandler Handler handler) {
 
         mHandler = handler;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 7e398bb..7d0ce5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -37,6 +37,7 @@
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.logging.NotifEvent;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -76,12 +77,16 @@
     private final Ranking mTmpRanking = new Ranking();
     private final boolean mUsePeopleFiltering;
     private final NotifLog mNotifLog;
+    private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
 
     @Inject
-    public NotificationData(NotificationSectionsFeatureManager sectionsFeatureManager,
-            NotifLog notifLog) {
+    public NotificationData(
+            NotificationSectionsFeatureManager sectionsFeatureManager,
+            NotifLog notifLog,
+            PeopleNotificationIdentifier peopleNotificationIdentifier) {
         mUsePeopleFiltering = sectionsFeatureManager.isFilteringEnabled();
         mNotifLog = notifLog;
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
     }
 
     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
@@ -460,8 +465,7 @@
     }
 
     private boolean isPeopleNotification(NotificationEntry e) {
-        return e.getSbn().getNotification().getNotificationStyle()
-                == Notification.MessagingStyle.class;
+        return mPeopleNotificationIdentifier.isPeopleNotification(e.getSbn());
     }
 
     public void dump(PrintWriter pw, String indent) {
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 8c067b7..4570989 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
@@ -24,14 +24,24 @@
 
     @Binds
     abstract fun peopleHubSectionFooterViewController(
-        viewAdapter: PeopleHubSectionFooterViewAdapterImpl
+        impl: PeopleHubSectionFooterViewAdapterImpl
     ): PeopleHubSectionFooterViewAdapter
 
     @Binds
-    abstract fun peopleHubDataSource(s: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
+    abstract fun peopleHubDataSource(impl: PeopleHubDataSourceImpl): DataSource<PeopleHubModel>
 
     @Binds
     abstract fun peopleHubViewModelFactoryDataSource(
-        dataSource: PeopleHubViewModelFactoryDataSourceImpl
+        impl: PeopleHubViewModelFactoryDataSourceImpl
     ): DataSource<PeopleHubViewModelFactory>
+
+    @Binds
+    abstract fun peopleNotificationIdentifier(
+        impl: PeopleNotificationIdentifierImpl
+    ): PeopleNotificationIdentifier
+
+    @Binds
+    abstract fun notificationPersonExtractor(
+        pluginImpl: NotificationPersonExtractorPluginBoundary
+    ): NotificationPersonExtractor
 }
\ No newline at end of file
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 c9ea559..fe257d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -17,11 +17,14 @@
 package com.android.systemui.statusbar.notification.people
 
 import android.app.Notification
+import android.content.Context
 import android.graphics.Canvas
 import android.graphics.ColorFilter
 import android.graphics.PixelFormat
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
 import android.util.TypedValue
 import android.view.View
 import android.view.ViewGroup
@@ -30,59 +33,112 @@
 import com.android.internal.widget.MessagingGroup
 import com.android.launcher3.icons.BaseIconFactory
 import com.android.systemui.R
+import com.android.systemui.plugins.NotificationPersonExtractorPlugin
 import com.android.systemui.statusbar.notification.NotificationEntryListener
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.policy.ExtensionController
 import java.util.ArrayDeque
 import javax.inject.Inject
 import javax.inject.Singleton
 
 private const val MAX_STORED_INACTIVE_PEOPLE = 10
 
-@Singleton
-class PeopleHubDataSourceImpl @Inject constructor(
-    notificationEntryManager: NotificationEntryManager,
-    private val peopleHubManager: PeopleHubManager
-) : DataSource<PeopleHubModel> {
+interface NotificationPersonExtractor {
+    fun extractPerson(sbn: StatusBarNotification): PersonModel?
+    fun extractPersonKey(sbn: StatusBarNotification): String?
+}
 
-    private var dataListener: DataListener<PeopleHubModel>? = null
+@Singleton
+class NotificationPersonExtractorPluginBoundary @Inject constructor(
+    extensionController: ExtensionController,
+    private val context: Context
+) : NotificationPersonExtractor {
+
+    private var plugin: NotificationPersonExtractorPlugin? = null
 
     init {
-        notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
-            override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
-                    addVisibleEntry(entry)
-
-            override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
-
-            override fun onPostEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
-
-            override fun onEntryRemoved(
-                entry: NotificationEntry,
-                visibility: NotificationVisibility?,
-                removedByUser: Boolean
-            ) = removeVisibleEntry(entry)
-        })
+        plugin = extensionController
+                .newExtension(NotificationPersonExtractorPlugin::class.java)
+                .withPlugin(NotificationPersonExtractorPlugin::class.java)
+                .withCallback { extractor ->
+                    plugin = extractor
+                }
+                .build()
+                .get()
     }
 
-    private fun removeVisibleEntry(entry: NotificationEntry?) {
-        if (entry?.extractPersonKey()?.let(peopleHubManager::removeActivePerson) == true) {
+    override fun extractPerson(sbn: StatusBarNotification) =
+            plugin?.extractPerson(sbn)?.let { data ->
+                val badged = addBadgeToDrawable(data.avatar, context, sbn.packageName, sbn.user)
+                PersonModel(data.key, data.name, badged, data.clickIntent)
+            }
+
+    override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
+}
+
+@Singleton
+class PeopleHubDataSourceImpl @Inject constructor(
+    private val notificationEntryManager: NotificationEntryManager,
+    private val peopleHubManager: PeopleHubManager,
+    private val extractor: NotificationPersonExtractor
+) : DataSource<PeopleHubModel> {
+
+    private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
+
+    private val notificationEntryListener = object : NotificationEntryListener {
+        override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
+                addVisibleEntry(entry)
+
+        override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
+
+        override fun onPostEntryUpdated(entry: NotificationEntry) = addVisibleEntry(entry)
+
+        override fun onEntryRemoved(
+            entry: NotificationEntry,
+            visibility: NotificationVisibility?,
+            removedByUser: Boolean
+        ) = removeVisibleEntry(entry)
+    }
+
+    private fun removeVisibleEntry(entry: NotificationEntry) {
+        val key = extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey()
+        if (key?.let(peopleHubManager::removeActivePerson) == true) {
             updateUi()
         }
     }
 
-    private fun addVisibleEntry(entry: NotificationEntry?) {
-        if (entry?.extractPerson()?.let(peopleHubManager::addActivePerson) == true) {
+    private fun addVisibleEntry(entry: NotificationEntry) {
+        val personModel = extractor.extractPerson(entry.sbn) ?: entry.extractPerson()
+        if (personModel?.let(peopleHubManager::addActivePerson) == true) {
             updateUi()
         }
     }
 
-    override fun setListener(listener: DataListener<PeopleHubModel>) {
-        this.dataListener = listener
-        updateUi()
+    override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
+        val registerWithNotificationEntryManager = dataListeners.isEmpty()
+        dataListeners.add(listener)
+        if (registerWithNotificationEntryManager) {
+            notificationEntryManager.addNotificationEntryListener(notificationEntryListener)
+        } else {
+            listener.onDataChanged(peopleHubManager.getPeopleHubModel())
+        }
+        return object : Subscription {
+            override fun unsubscribe() {
+                dataListeners.remove(listener)
+                if (dataListeners.isEmpty()) {
+                    notificationEntryManager
+                            .removeNotificationEntryListener(notificationEntryListener)
+                }
+            }
+        }
     }
 
     private fun updateUi() {
-        dataListener?.onDataChanged(peopleHubManager.getPeopleHubModel())
+        val model = peopleHubManager.getPeopleHubModel()
+        for (listener in dataListeners) {
+            listener.onDataChanged(model)
+        }
     }
 }
 
@@ -124,7 +180,6 @@
     if (!isMessagingNotification()) {
         return null
     }
-
     val clickIntent = sbn.notification.contentIntent
             ?: return null
     val extras = sbn.notification.extras
@@ -132,12 +187,19 @@
             ?: extras.getString(Notification.EXTRA_TITLE)
             ?: return null
     val drawable = extractAvatarFromRow(this) ?: return null
+    val badgedAvatar = addBadgeToDrawable(drawable, row.context, sbn.packageName, sbn.user)
+    return PersonModel(key, name, badgedAvatar, clickIntent)
+}
 
-    val context = row.context
+private fun addBadgeToDrawable(
+    drawable: Drawable,
+    context: Context,
+    packageName: String,
+    user: UserHandle
+): Drawable {
     val pm = context.packageManager
-    val appInfo = pm.getApplicationInfo(sbn.packageName, 0)
-
-    val badgedAvatar = object : Drawable() {
+    val appInfo = pm.getApplicationInfoAsUser(packageName, 0, user)
+    return object : Drawable() {
         override fun draw(canvas: Canvas) {
             val iconBounds = getBounds()
             val factory = object : BaseIconFactory(
@@ -147,7 +209,7 @@
                     true) {}
             val badge = factory.createBadgedIconBitmap(
                     appInfo.loadIcon(pm),
-                    sbn.user,
+                    user,
                     true,
                     appInfo.isInstantApp,
                     null)
@@ -157,7 +219,7 @@
                         colorFilter = drawable.colorFilter
                         val badgeWidth = TypedValue.applyDimension(
                                 TypedValue.COMPLEX_UNIT_DIP,
-                                16f,
+                                15f,
                                 context.resources.displayMetrics
                         ).toInt()
                         setBounds(
@@ -182,8 +244,6 @@
         @PixelFormat.Opacity
         override fun getOpacity(): Int = PixelFormat.OPAQUE
     }
-
-    return PersonModel(key, name, badgedAvatar, clickIntent)
 }
 
 private fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
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 8d1253b..5c35408 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
@@ -60,8 +60,9 @@
     private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubViewModelFactory>
 ) : PeopleHubSectionFooterViewAdapter {
 
-    override fun bindView(viewBoundary: PeopleHubSectionFooterViewBoundary) =
-            dataSource.setListener(PeopleHubDataListenerImpl(viewBoundary))
+    override fun bindView(viewBoundary: PeopleHubSectionFooterViewBoundary) {
+        dataSource.registerListener(PeopleHubDataListenerImpl(viewBoundary))
+    }
 }
 
 private class PeopleHubDataListenerImpl(
@@ -92,8 +93,8 @@
     private val dataSource: DataSource<@JvmSuppressWildcards PeopleHubModel>
 ) : DataSource<PeopleHubViewModelFactory> {
 
-    override fun setListener(listener: DataListener<PeopleHubViewModelFactory>) =
-            dataSource.setListener(PeopleHubModelListenerImpl(activityStarter, listener))
+    override fun registerListener(listener: DataListener<PeopleHubViewModelFactory>) =
+            dataSource.registerListener(PeopleHubModelListenerImpl(activityStarter, listener))
 }
 
 private class PeopleHubModelListenerImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
new file mode 100644
index 0000000..bfd4070
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -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 com.android.systemui.statusbar.notification.people
+
+import android.app.Notification
+import android.service.notification.StatusBarNotification
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface PeopleNotificationIdentifier {
+    fun isPeopleNotification(sbn: StatusBarNotification): Boolean
+}
+
+@Singleton
+class PeopleNotificationIdentifierImpl @Inject constructor(
+    private val personExtractor: NotificationPersonExtractor
+) : PeopleNotificationIdentifier {
+
+    override fun isPeopleNotification(sbn: StatusBarNotification) =
+            sbn.notification.notificationStyle == Notification.MessagingStyle::class.java ||
+                    personExtractor.extractPersonKey(sbn) != null
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
index 33e3bb8..3ca3792 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt
@@ -28,10 +28,17 @@
 
 /** Boundary between a View and data pipeline, as seen by the View. */
 interface DataSource<out T> {
-    fun setListener(listener: DataListener<T>)
+    fun registerListener(listener: DataListener<T>): Subscription
+}
+
+/** Represents a registration with a [DataSource]. */
+interface Subscription {
+    /** Removes the previously registered [DataListener] from the [DataSource] */
+    fun unsubscribe()
 }
 
 /** Transform all data coming out of this [DataSource] using the given [mapper]. */
 fun <S, T> DataSource<S>.map(mapper: (S) -> T): DataSource<T> = object : DataSource<T> {
-    override fun setListener(listener: DataListener<T>) = setListener(listener.contraMap(mapper))
+    override fun registerListener(listener: DataListener<T>) =
+            registerListener(listener.contraMap(mapper))
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 5d5c09e..9bc0ca4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -28,6 +28,7 @@
 import android.media.session.PlaybackState;
 import android.metrics.LogMaker;
 import android.os.Handler;
+import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -41,9 +42,12 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.widget.MediaNotificationView;
 import com.android.systemui.Dependency;
+import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.TransformableView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
 
 import java.util.Timer;
 import java.util.TimerTask;
@@ -178,6 +182,26 @@
         final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
                 .getParcelable(Notification.EXTRA_MEDIA_SESSION);
 
+        int flag = Settings.System.getInt(mContext.getContentResolver(), "qs_media_player", 0);
+        if (flag == 1) {
+            StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class);
+            QuickQSPanel panel = ctrl.getStatusBarView().findViewById(
+                    com.android.systemui.R.id.quick_qs_panel);
+            panel.getMediaPlayer().setMediaSession(token,
+                    mRow.getStatusBarNotification().getNotification().getSmallIcon(),
+                    getNotificationHeader().getOriginalIconColor(),
+                    mRow.getCurrentBackgroundTint(),
+                    mActions);
+            QSPanel bigPanel = ctrl.getStatusBarView().findViewById(
+                    com.android.systemui.R.id.quick_settings_panel);
+            bigPanel.addMediaSession(token,
+                    mRow.getStatusBarNotification().getNotification().getSmallIcon(),
+                    getNotificationHeader().getOriginalIconColor(),
+                    mRow.getCurrentBackgroundTint(),
+                    mActions,
+                    mRow.getStatusBarNotification());
+        }
+
         boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
         if (token == null || (COMPACT_MEDIA_TAG.equals(mView.getTag()) && !showCompactSeekbar)) {
             if (mSeekBarView != null) {
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 bd87d77..54d4066 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
@@ -131,6 +131,7 @@
         }
         mInitialized = true;
         reinflateViews(layoutInflater);
+        mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
         mConfigurationController.addCallback(mConfigurationListener);
     }
 
@@ -172,10 +173,6 @@
         if (oldPeopleHubPos != -1) {
             mParent.addView(mPeopleHubView, oldPeopleHubPos);
         }
-
-        if (!mInitialized) {
-            mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary);
-        }
     }
 
     /** Listener for when the "clear all" buttton is clciked on the gentle notification header. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index 008464e..f9b9367 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -25,10 +23,10 @@
 import android.view.IWindowManager;
 import android.view.MotionEvent;
 
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /** A controller to control all auto-hide things. */
 public class AutoHideController {
@@ -54,7 +52,7 @@
     };
 
     @Inject
-    public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler,
+    public AutoHideController(Context context, @MainHandler Handler handler,
             NotificationRemoteInputManager notificationRemoteInputManager,
             IWindowManager iWindowManager) {
         mHandler = handler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 007c50c..837517e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -21,7 +21,7 @@
 import android.provider.Settings.Secure;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
+import com.android.systemui.dagger.qualifiers.BgHandler;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.SecureSetting;
@@ -33,7 +33,6 @@
 import com.android.systemui.statusbar.policy.HotspotController.Callback;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /**
  * Manages which tiles should be automatically added to QS.
@@ -58,7 +57,7 @@
 
     @Inject
     public AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host,
-            @Named(Dependency.BG_HANDLER_NAME) Handler handler,
+            @BgHandler Handler handler,
             HotspotController hotspotController,
             DataSaverController dataSaverController,
             ManagedProfileController managedProfileController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 548afd5..b9516cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
 import android.metrics.LogMaker;
 import android.os.Handler;
@@ -34,6 +35,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dependency;
+import com.android.systemui.dagger.qualifiers.MainResources;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -44,6 +46,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+import javax.inject.Inject;
+
 /**
  * Controller which coordinates all the biometric unlocking actions with the UI.
  */
@@ -145,31 +149,16 @@
     private boolean mHasScreenTurnedOnSinceAuthenticating;
     private boolean mFadedAwayAfterWakeAndUnlock;
 
-    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    private final MetricsLogger mMetricsLogger;
 
-    public BiometricUnlockController(
-            Context context,
-            DozeScrimController dozeScrimController,
-            KeyguardViewMediator keyguardViewMediator,
-            ScrimController scrimController,
-            StatusBar statusBar,
-            KeyguardStateController keyguardStateController, Handler handler,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            KeyguardBypassController keyguardBypassController,
-            DozeParameters dozeParameters) {
-        this(context, dozeScrimController, keyguardViewMediator, scrimController, statusBar,
-                keyguardStateController, handler, keyguardUpdateMonitor,
-                context.getResources()
-                        .getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze),
-                keyguardBypassController, dozeParameters);
-    }
-
-    @VisibleForTesting
-    protected BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
+    @Inject
+    public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
             StatusBar statusBar, KeyguardStateController keyguardStateController, Handler handler,
-            KeyguardUpdateMonitor keyguardUpdateMonitor, int wakeUpDelay,
-            KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            @MainResources Resources resources,
+            KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters,
+            MetricsLogger metricsLogger) {
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -185,9 +174,10 @@
         mStatusBar = statusBar;
         mKeyguardStateController = keyguardStateController;
         mHandler = handler;
-        mWakeUpDelay = wakeUpDelay;
+        mWakeUpDelay = resources.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze);
         mKeyguardBypassController = keyguardBypassController;
         mKeyguardBypassController.setUnlockController(this);
+        mMetricsLogger = metricsLogger;
     }
 
     public void setStatusBarKeyguardViewManager(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 28dac87..50d33a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -25,8 +25,8 @@
 import android.util.MathUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.DependencyProvider;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainResources;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.tuner.TunerService;
@@ -59,7 +59,7 @@
 
     @Inject
     protected DozeParameters(
-            @DependencyProvider.MainResources Resources resources,
+            @MainResources Resources resources,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
             AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
             PowerManager powerManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index c9c38a0..1c8e832 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -54,9 +54,7 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.WindowManagerWrapper;
 
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
@@ -70,15 +68,6 @@
     private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
             "gestures.back_timeout", 250);
 
-    private final PinnedStackListener mImeChangedListener = new PinnedStackListener() {
-        @Override
-        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
-            // No need to thread jump, assignments are atomic
-            mImeHeight = imeVisible ? imeHeight : 0;
-            // TODO: Probably cancel any existing gesture
-        }
-    };
-
     private ISystemGestureExclusionListener mGestureExclusionListener =
             new ISystemGestureExclusionListener.Stub() {
                 @Override
@@ -126,8 +115,6 @@
     private boolean mInRejectedExclusion = false;
     private boolean mIsOnLeftEdge;
 
-    private int mImeHeight = 0;
-
     private boolean mIsAttached;
     private boolean mIsGesturalModeEnabled;
     private boolean mIsEnabled;
@@ -227,7 +214,6 @@
         }
 
         if (!mIsEnabled) {
-            WindowManagerWrapper.getInstance().removePinnedStackListener(mImeChangedListener);
             mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
 
             try {
@@ -244,7 +230,6 @@
                     mContext.getMainThreadHandler());
 
             try {
-                WindowManagerWrapper.getInstance().addPinnedStackListener(mImeChangedListener);
                 WindowManagerGlobal.getWindowManagerService()
                         .registerSystemGestureExclusionListener(
                                 mGestureExclusionListener, mDisplayId);
@@ -301,11 +286,6 @@
     }
 
     private boolean isWithinTouchRegion(int x, int y) {
-        // Disallow if over the IME
-        if (y > (mDisplaySize.y - Math.max(mImeHeight, mNavBarHeight))) {
-            return false;
-        }
-
         // Disallow if too far from the edge
         if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
             return false;
@@ -483,7 +463,6 @@
         pw.println("  mInRejectedExclusion" + mInRejectedExclusion);
         pw.println("  mExcludeRegion=" + mExcludeRegion);
         pw.println("  mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
-        pw.println("  mImeHeight=" + mImeHeight);
         pw.println("  mIsAttached=" + mIsAttached);
         pw.println("  mEdgeWidth=" + mEdgeWidth);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 89051cd..30fe68a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -770,6 +770,11 @@
         int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
         int topMargin =
                 res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
+        int flag = Settings.System.getInt(mContext.getContentResolver(), "qs_media_player", 0);
+        if (flag == 1) {
+            topMargin = res.getDimensionPixelOffset(
+                    com.android.internal.R.dimen.quick_qs_total_height_with_media);
+        }
         lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
         if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
                 || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
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 c50b1bf..6064fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -42,10 +42,10 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.DejankUtils;
-import com.android.systemui.DependencyProvider;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.MainResources;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -188,7 +188,7 @@
     @Inject
     public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters,
             AlarmManager alarmManager, KeyguardStateController keyguardStateController,
-            @DependencyProvider.MainResources Resources resources,
+            @MainResources Resources resources,
             DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor, SysuiColorExtractor sysuiColorExtractor) {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index e0597159..13055ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -39,15 +39,20 @@
         @Override
         public void prepare(ScrimState previousState) {
             mFrontTint = Color.BLACK;
-            mBehindTint = previousState.mBehindTint;
+            mBehindTint = Color.BLACK;
             mBubbleTint = previousState.mBubbleTint;
 
             mFrontAlpha = 1f;
-            mBehindAlpha = previousState.mBehindAlpha;
+            mBehindAlpha = 1f;
             mBubbleAlpha = previousState.mBubbleAlpha;
 
             mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
         }
+
+        @Override
+        public boolean isLowPowerState() {
+            return true;
+        }
     },
 
     /**
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 2963c94..4e0af64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -356,7 +356,7 @@
     private VolumeComponent mVolumeComponent;
     private BrightnessMirrorController mBrightnessMirrorController;
     private boolean mBrightnessMirrorVisible;
-    protected BiometricUnlockController mBiometricUnlockController;
+    private BiometricUnlockController mBiometricUnlockController;
     private final LightBarController mLightBarController;
     private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
     protected LockscreenWallpaper mLockscreenWallpaper;
@@ -398,6 +398,7 @@
     private final StatusBarWindowViewController.Builder mStatusBarWindowViewControllerBuilder;
     private final NotifLog mNotifLog;
     private final DozeParameters mDozeParameters;
+    private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
 
     // expanded notifications
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -691,7 +692,8 @@
             NotifLog notifLog,
             DozeParameters dozeParameters,
             ScrimController scrimController,
-            Lazy<LockscreenWallpaper> lockscreenWallpaperLazy) {
+            Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
+            Lazy<BiometricUnlockController> biometricUnlockControllerLazy) {
         super(context);
         mFeatureFlags = featureFlags;
         mLightBarController = lightBarController;
@@ -751,6 +753,7 @@
         mDozeParameters = dozeParameters;
         mScrimController = scrimController;
         mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
+        mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
 
         mBubbleExpandListener =
                 (isExpanding, key) -> {
@@ -1370,11 +1373,7 @@
 
     protected void startKeyguard() {
         Trace.beginSection("StatusBar#startKeyguard");
-        mBiometricUnlockController = new BiometricUnlockController(mContext,
-                mDozeScrimController, mKeyguardViewMediator,
-                mScrimController, this, mKeyguardStateController, new Handler(),
-                mKeyguardUpdateMonitor, mKeyguardBypassController, mDozeParameters);
-        putComponent(BiometricUnlockController.class, mBiometricUnlockController);
+        mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
         mStatusBarKeyguardViewManager = mKeyguardViewMediator.registerStatusBar(this,
                 getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
                 mStatusBarWindow.findViewById(R.id.lock_icon_container), mStackScroller,
@@ -4323,13 +4322,21 @@
 
         @Override
         public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
-            if (onDisplayOffCallback != null) {
+            if (mPendingScreenOffCallback != null) {
                 Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
             }
             mPendingScreenOffCallback = onDisplayOffCallback;
             updateScrimController();
         }
 
+        @Override
+        public void cancelGentleSleep() {
+            mPendingScreenOffCallback = null;
+            if (mScrimController.getState() == ScrimState.OFF) {
+                updateScrimController();
+            }
+        }
+
         /**
          * When the dozing host is waiting for scrims to fade out to change the display state.
          */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 0a2fb2e..76683b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
-import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
-
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothAdapter;
@@ -36,6 +33,8 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.dagger.qualifiers.BgLooper;
+import com.android.systemui.dagger.qualifiers.MainLooper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -46,7 +45,6 @@
 import java.util.WeakHashMap;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -74,9 +72,8 @@
     /**
      */
     @Inject
-    public BluetoothControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
-            @Named(MAIN_LOOPER_NAME) Looper mainLooper,
-            @Nullable LocalBluetoothManager localBluetoothManager) {
+    public BluetoothControllerImpl(Context context, @BgLooper Looper bgLooper,
+            @MainLooper Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager) {
         mLocalBluetoothManager = localBluetoothManager;
         mBgHandler = new Handler(bgLooper);
         mHandler = new H(mainLooper);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
index 089d5c9..0a40b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java
@@ -14,8 +14,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -26,12 +24,12 @@
 import android.provider.Settings.Secure;
 import android.util.Log;
 
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.settings.CurrentUserTracker;
 
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -51,8 +49,7 @@
     /**
      */
     @Inject
-    public DeviceProvisionedControllerImpl(Context context,
-            @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+    public DeviceProvisionedControllerImpl(Context context, @MainHandler Handler mainHandler) {
         super(context);
         mContext = context;
         mContentResolver = context.getContentResolver();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
index cade5dc..3ce6239 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java
@@ -57,7 +57,7 @@
         ExtensionBuilder<T> withCallback(Consumer<T> callback);
         ExtensionBuilder<T> withUiMode(int mode, Supplier<T> def);
         ExtensionBuilder<T> withFeature(String feature, Supplier<T> def);
-        Extension build();
+        Extension<T> build();
     }
 
     public interface PluginConverter<T, P> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index fd030d1..eeef726 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -135,7 +135,7 @@
         }
 
         @Override
-        public ExtensionController.Extension build() {
+        public ExtensionController.Extension<T> build() {
             // Sort items in ascending order
             Collections.sort(mExtension.mProducers, Comparator.comparingInt(Item::sortOrder));
             mExtension.notifyChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 1c6d12f..8f201c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.app.ActivityManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -26,12 +24,13 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import com.android.systemui.dagger.qualifiers.MainHandler;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -56,7 +55,7 @@
     /**
      */
     @Inject
-    public HotspotControllerImpl(Context context, @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
+    public HotspotControllerImpl(Context context, @MainHandler Handler mainHandler) {
         mContext = context;
         mConnectivityManager =
                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 683cdbb..5a97eed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.policy;
 
 import static com.android.settingslib.Utils.updateLocationEnabled;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
 
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
@@ -36,13 +35,13 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.systemui.dagger.qualifiers.BgLooper;
 import com.android.systemui.util.Utils;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -66,7 +65,7 @@
     private final H mHandler = new H();
 
     @Inject
-    public LocationControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper) {
+    public LocationControllerImpl(Context context, @BgLooper Looper bgLooper) {
         mContext = context;
 
         // Register to listen for changes in location settings.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 7b5d489..60784c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -24,7 +24,6 @@
 import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
 
 import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -65,6 +64,7 @@
 import com.android.systemui.DemoMode;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.BgLooper;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup;
@@ -81,7 +81,6 @@
 import java.util.Map;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /** Platform implementation of the network controller. **/
@@ -170,7 +169,7 @@
      * Construct this controller object and register for updates.
      */
     @Inject
-    public NetworkControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
+    public NetworkControllerImpl(Context context, @BgLooper Looper bgLooper,
             DeviceProvisionedController deviceProvisionedController) {
         this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index d88ae78..6edd75b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -15,8 +15,6 @@
  */
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.Dependency.BG_HANDLER_NAME;
-
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -50,6 +48,7 @@
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.BgHandler;
 import com.android.systemui.settings.CurrentUserTracker;
 
 import java.io.FileDescriptor;
@@ -57,7 +56,6 @@
 import java.util.ArrayList;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -102,7 +100,7 @@
     /**
      */
     @Inject
-    public SecurityControllerImpl(Context context, @Named(BG_HANDLER_NAME) Handler bgHandler) {
+    public SecurityControllerImpl(Context context, @BgHandler Handler bgHandler) {
         this(context, bgHandler, null);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index 655c29c..347d300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.app.RemoteInput;
 import android.content.Context;
 import android.content.res.Resources;
@@ -30,9 +28,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 @Singleton
@@ -67,7 +65,7 @@
     private final KeyValueListParser mParser = new KeyValueListParser(',');
 
     @Inject
-    public SmartReplyConstants(@Named(MAIN_HANDLER_NAME) Handler handler, Context context) {
+    public SmartReplyConstants(@MainHandler Handler handler, Context context) {
         mHandler = handler;
         mContext = context;
         final Resources resources = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index bcfbdac..f2d2fae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -18,7 +18,6 @@
 
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
 
 import android.app.ActivityManager;
 import android.app.Dialog;
@@ -58,6 +57,7 @@
 import com.android.systemui.Prefs.Key;
 import com.android.systemui.R;
 import com.android.systemui.SystemUISecondaryUserService;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.tiles.UserDetailView;
@@ -70,7 +70,6 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -110,7 +109,7 @@
 
     @Inject
     public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
-            @Named(MAIN_HANDLER_NAME) Handler handler, ActivityStarter activityStarter) {
+            @MainHandler Handler handler, ActivityStarter activityStarter) {
         mContext = context;
         if (!UserManager.isGuestUserEphemeral()) {
             mGuestResumeSessionReceiver.register(context);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 29c42d2..1c7a195 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.NotificationManager;
@@ -41,6 +39,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.qs.GlobalSetting;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.util.Utils;
@@ -51,7 +50,6 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /** Platform implementation of the zen mode controller. **/
@@ -79,7 +77,7 @@
     private NotificationManager.Policy mConsolidatedNotificationPolicy;
 
     @Inject
-    public ZenModeControllerImpl(Context context, @Named(MAIN_HANDLER_NAME) Handler handler) {
+    public ZenModeControllerImpl(Context context, @MainHandler Handler handler) {
         super(context);
         mContext = context;
         mModeSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index a55e2cf..2d6027c 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -15,8 +15,6 @@
  */
 package com.android.systemui.tuner;
 
-import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
-
 import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -36,6 +34,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.DemoMode;
+import com.android.systemui.dagger.qualifiers.MainHandler;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -46,7 +45,6 @@
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 
@@ -83,7 +81,7 @@
     /**
      */
     @Inject
-    public TunerServiceImpl(Context context, @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+    public TunerServiceImpl(Context context, @MainHandler Handler mainHandler,
             LeakDetector leakDetector) {
         mContext = context;
         mContentResolver = mContext.getContentResolver();
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 7e801da..5ed027d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -26,7 +26,7 @@
 import com.android.keyguard.KeyguardClockSwitch;
 import com.android.keyguard.KeyguardMessageArea;
 import com.android.keyguard.KeyguardSliceView;
-import com.android.systemui.SystemUIRootComponent;
+import com.android.systemui.dagger.SystemUIRootComponent;
 import com.android.systemui.qs.QSCarrierGroup;
 import com.android.systemui.qs.QSFooterImpl;
 import com.android.systemui.qs.QSPanel;
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 63db755..bff405c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -20,7 +20,6 @@
 import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
 
 import static com.android.internal.logging.MetricsLogger.VIEW_UNKNOWN;
-import static com.android.systemui.Dependency.BG_LOOPER_NAME;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -48,6 +47,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.qualifiers.BgLooper;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSHost;
@@ -59,7 +59,6 @@
 import java.util.List;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 /**
@@ -110,7 +109,7 @@
     @Inject
     public GarbageMonitor(
             Context context,
-            @Named(BG_LOOPER_NAME) Looper bgLooper,
+            @BgLooper Looper bgLooper,
             LeakDetector leakDetector,
             LeakReporter leakReporter) {
         mContext = context.getApplicationContext();
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index fdffc43..a96977a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -25,8 +25,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.DependencyProvider;
 import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.MainResources;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -65,7 +65,7 @@
     };
 
     @Inject
-    public ProximitySensor(@DependencyProvider.MainResources Resources resources,
+    public ProximitySensor(@MainResources Resources resources,
             AsyncSensorManager sensorManager) {
         mSensorManager = sensorManager;
         Sensor sensor = findBrightnessSensor(resources);
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 e1eb3b0..b089b74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -38,15 +38,18 @@
 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;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.face.FaceManager;
 import android.os.Bundle;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper.RunWithLooper;
 
+import com.android.internal.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -89,9 +92,9 @@
 
         when(context.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
-            .thenReturn(true);
+                .thenReturn(true);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
-            .thenReturn(true);
+                .thenReturn(true);
 
         when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
         when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
@@ -170,20 +173,34 @@
     @Test
     public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
         showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
-        mAuthController.onBiometricAuthenticated(true, null /* failureReason */);
+        mAuthController.onBiometricAuthenticated();
         verify(mDialog1).onAuthenticationSucceeded();
     }
 
     @Test
-    public void testOnAuthenticationFailedInvoked_whenSystemRequested() {
+    public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
         showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
-        final String failureReason = "failure reason";
-        mAuthController.onBiometricAuthenticated(false, failureReason);
+        mAuthController.onBiometricError(BiometricAuthenticator.TYPE_NONE,
+                BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
+                0 /* vendorCode */);
 
         ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
         verify(mDialog1).onAuthenticationFailed(captor.capture());
 
-        assertEquals(captor.getValue(), failureReason);
+        assertEquals(captor.getValue(), mContext.getString(R.string.biometric_not_recognized));
+    }
+
+    @Test
+    public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
+        showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
+        final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
+        final int vendorCode = 0;
+        mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
+
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        verify(mDialog1).onAuthenticationFailed(captor.capture());
+
+        assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
     }
 
     @Test
@@ -199,27 +216,27 @@
     }
 
     @Test
-    public void testOnErrorInvoked_whenSystemRequested() {
+    public void testOnErrorInvoked_whenSystemRequested() throws Exception {
         showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
         final int error = 1;
-        final String errMessage = "error message";
-        mAuthController.onBiometricError(error, errMessage);
+        final int vendorCode = 0;
+        mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
 
         ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
         verify(mDialog1).onError(captor.capture());
 
-        assertEquals(captor.getValue(), errMessage);
+        assertEquals(captor.getValue(), FaceManager.getErrorString(mContext, error, vendorCode));
     }
 
     @Test
     public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
         showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
-        final String errorString = "lockout";
+        final int vendorCode = 0;
 
         when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
 
-        mAuthController.onBiometricError(error, errorString);
+        mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
         verify(mDialog1, never()).onError(anyString());
         verify(mDialog1).animateToCredentialUI();
     }
@@ -228,11 +245,11 @@
     public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
         showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-        final String errorString = "lockout_permanent";
+        final int vendorCode = 0;
 
         when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
 
-        mAuthController.onBiometricError(error, errorString);
+        mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
         verify(mDialog1, never()).onError(anyString());
         verify(mDialog1).animateToCredentialUI();
     }
@@ -241,12 +258,12 @@
     public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
         showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
-        final String errorString = "lockout";
+        final int vendorCode = 0;
 
         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
 
-        mAuthController.onBiometricError(error, errorString);
-        verify(mDialog1).onError(eq(errorString));
+        mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
+        verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
         verify(mDialog1, never()).animateToCredentialUI();
     }
 
@@ -254,12 +271,12 @@
     public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
         showDialog(Authenticator.TYPE_BIOMETRIC, BiometricPrompt.TYPE_FACE);
         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-        final String errorString = "lockout_permanent";
+        final int vendorCode = 0;
 
         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
 
-        mAuthController.onBiometricError(error, errorString);
-        verify(mDialog1).onError(eq(errorString));
+        mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
+        verify(mDialog1).onError(eq(FaceManager.getErrorString(mContext, error, vendorCode)));
         verify(mDialog1, never()).animateToCredentialUI();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index eb198c6..8c9ae71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -418,10 +418,9 @@
 
     @Test
     public void testOnBiometricAuthenticated() {
-        String failureReason = "test_failure_reason";
-        mCommandQueue.onBiometricAuthenticated(true /* authenticated */, failureReason);
+        mCommandQueue.onBiometricAuthenticated();
         waitForIdleSync();
-        verify(mCallbacks).onBiometricAuthenticated(eq(true), eq(failureReason));
+        verify(mCallbacks).onBiometricAuthenticated();
     }
 
     @Test
@@ -434,11 +433,12 @@
 
     @Test
     public void testOnBiometricError() {
-        final int errorCode = 1;
-        String errorMessage = "test_error_message";
-        mCommandQueue.onBiometricError(errorCode, errorMessage);
+        final int modality = 1;
+        final int error = 2;
+        final int vendorCode = 3;
+        mCommandQueue.onBiometricError(modality, error, vendorCode);
         waitForIdleSync();
-        verify(mCallbacks).onBiometricError(eq(errorCode), eq(errorMessage));
+        verify(mCallbacks).onBiometricError(eq(modality), eq(error), eq(vendorCode));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index ebdf851..bde7ef9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -82,6 +82,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -148,8 +149,12 @@
         private final CountDownLatch mCountDownLatch;
 
         TestableNotificationEntryManager() {
-            super(new NotificationData(mock(NotificationSectionsFeatureManager.class),
-                    mock(NotifLog.class)), mock(NotifLog.class));
+            super(
+                    new NotificationData(
+                            mock(NotificationSectionsFeatureManager.class),
+                            mock(NotifLog.class),
+                            mock(PeopleNotificationIdentifier.class)),
+                    mock(NotifLog.class));
             mCountDownLatch = new CountDownLatch(1);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 9202c51..2435bb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -46,6 +46,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -78,8 +79,10 @@
 
     // TODO: Remove this once EntryManager no longer needs to be mocked
     private NotificationData mNotificationData =
-            new NotificationData(new NotificationSectionsFeatureManager(
-                    new DeviceConfigProxyFake(), mContext), mock(NotifLog.class));
+            new NotificationData(
+                    new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext),
+                    mock(NotifLog.class),
+                    mock(PeopleNotificationIdentifier.class));
 
     private int mNextNotifId = 0;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index 640984b..dba0174 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -81,6 +81,7 @@
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -639,7 +640,10 @@
 
     public static class TestableNotificationData extends NotificationData {
         public TestableNotificationData(NotificationSectionsFeatureManager sectionsFeatureManager) {
-            super(sectionsFeatureManager, mock(NotifLog.class));
+            super(
+                    sectionsFeatureManager,
+                    mock(NotifLog.class),
+                    mock(PeopleNotificationIdentifier.class));
         }
 
         public static final String OVERRIDE_RANK = "r";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 72bea56..4a0b371 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -34,7 +34,9 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.testing.TestableResources;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -78,11 +80,14 @@
     private KeyguardBypassController mKeyguardBypassController;
     @Mock
     private DozeParameters mDozeParameters;
+    @Mock
+    private MetricsLogger mMetricsLogger;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        TestableResources res = getContext().getOrCreateTestableResources();
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
         when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
@@ -92,10 +97,11 @@
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
         mDependency.injectTestDependency(StatusBarWindowController.class,
                 mStatusBarWindowController);
+        res.addOverride(com.android.internal.R.integer.config_wakeUpDelayDoze, 0);
         mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
                 mKeyguardViewMediator, mScrimController, mStatusBar, mKeyguardStateController,
-                mHandler, mUpdateMonitor, 0 /* wakeUpDelay */, mKeyguardBypassController,
-                mDozeParameters);
+                mHandler, mUpdateMonitor, res.getResources(), mKeyguardBypassController,
+                mDozeParameters, mMetricsLogger);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index cff6635..4853f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -236,8 +237,11 @@
                     mock(PluginManager.class),
                     mock(ShadeController.class),
                     mock(NotificationLockscreenUserManager.class),
-                    new NotificationEntryManager(new NotificationData(mock(
-                            NotificationSectionsFeatureManager.class), mock(NotifLog.class)),
+                    new NotificationEntryManager(
+                            new NotificationData(
+                                    mock(NotificationSectionsFeatureManager.class),
+                                    mock(NotifLog.class),
+                                    mock(PeopleNotificationIdentifier.class)),
                             mock(NotifLog.class)),
                     mock(KeyguardStateController.class),
                     statusBarStateController,
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 1b132b9..85c247e 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
@@ -64,6 +64,7 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 
@@ -249,7 +250,7 @@
         finishAnimationsImmediately();
 
         assertScrimAlpha(OPAQUE /* front */,
-                SEMI_TRANSPARENT /* back */,
+                OPAQUE /* back */,
                 TRANSPARENT /* bubble */);
 
         assertScrimTint(true /* front */,
@@ -858,6 +859,22 @@
                 mScrimForBubble.getDefaultFocusHighlightEnabled());
     }
 
+    @Test
+    public void testIsLowPowerMode() {
+        HashSet<ScrimState> lowPowerModeStates = new HashSet<>(Arrays.asList(
+                ScrimState.OFF, ScrimState.AOD, ScrimState.PULSING));
+        HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
+                ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, ScrimState.BOUNCER,
+                ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR, ScrimState.UNLOCKED,
+                ScrimState.BUBBLE_EXPANDED));
+
+        for (ScrimState state : ScrimState.values()) {
+            if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) {
+                Assert.fail("Scrim state not whitelisted nor blacklisted as low power mode");
+            }
+        }
+    }
+
     private void assertScrimTint(boolean front, boolean behind, boolean bubble) {
         Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
                         + " with scrim: " + getScrimName(mScrimInFront) + " and tint: "
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 03d0cea..f5e92e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -61,7 +61,9 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.DisplayMetrics;
 import android.util.SparseArray;
+import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
+import android.widget.LinearLayout;
 
 import androidx.test.filters.SmallTest;
 
@@ -70,6 +72,7 @@
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Dependency;
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.InitController;
@@ -173,6 +176,7 @@
     @Mock private ScrimController mScrimController;
     @Mock private DozeScrimController mDozeScrimController;
     @Mock private ArrayList<NotificationEntry> mNotificationList;
+    @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private NotificationData mNotificationData;
     @Mock private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
@@ -227,11 +231,14 @@
     @Mock private DozeParameters mDozeParameters;
     @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
     @Mock private LockscreenWallpaper mLockscreenWallpaper;
+    @Mock private LinearLayout mLockIconContainer;
+    @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mDependency.injectTestDependency(NotificationFilter.class, mNotificationFilter);
+        mDependency.injectMockDependency(KeyguardDismissUtil.class);
 
         IPowerManager powerManagerService = mock(IPowerManager.class);
         mPowerManager = new PowerManager(mContext, powerManagerService,
@@ -293,6 +300,7 @@
                 .thenReturn(mStatusBarWindowViewController);
 
         when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
+        when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
 
         mStatusBar = new StatusBar(
                 mContext,
@@ -356,13 +364,24 @@
                 mNotifLog,
                 mDozeParameters,
                 mScrimController,
-                mLockscreenWallpaperLazy);
+                mLockscreenWallpaperLazy,
+                mBiometricUnlockControllerLazy);
+
+        when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
+                mLockIconContainer);
+
+        when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
+                any(NotificationPanelView.class), any(BiometricUnlockController.class),
+                any(ViewGroup.class), any(ViewGroup.class), any(KeyguardBypassController.class)))
+                .thenReturn(mStatusBarKeyguardViewManager);
+
+        when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
+                mKeyguardVieMediatorCallback);
+
         // TODO: we should be able to call mStatusBar.start() and have all the below values
         // initialized automatically.
         mStatusBar.mComponents = mContext.getComponents();
-        mStatusBar.mStatusBarKeyguardViewManager = mStatusBarKeyguardViewManager;
         mStatusBar.mStatusBarWindow = mStatusBarWindowView;
-        mStatusBar.mBiometricUnlockController = mBiometricUnlockController;
         mStatusBar.mNotificationPanel = mNotificationPanelView;
         mStatusBar.mCommandQueue = mCommandQueue;
         mStatusBar.mDozeScrimController = mDozeScrimController;
@@ -373,6 +392,7 @@
         mStatusBar.mBarService = mBarService;
         mStatusBar.mStackScroller = mStackScroller;
         mStatusBar.mStatusBarWindowViewController = mStatusBarWindowViewController;
+        mStatusBar.startKeyguard();
         mStatusBar.putComponent(StatusBar.class, mStatusBar);
         Dependency.get(InitController.class).executePostInitTasks();
         entryManager.setUpForTest(mock(NotificationPresenter.class), mStackScroller,
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 5d6d1c9..5712566 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -253,6 +253,8 @@
     NOTE_NETWORK_LOGGED_IN = 744;
     // A partial connectivity network was detected during network validation
     NOTE_NETWORK_PARTIAL_CONNECTIVITY = 745;
+    // Private DNS is broken in strict mode
+    NOTE_NETWORK_PRIVATE_DNS_BROKEN = 746;
 
     // Notify the user that their work profile has been deleted
     // Package: android
diff --git a/services/art-profile b/services/art-profile
index cbc4627..a0338d5 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -20824,3 +20824,532 @@
 Lcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;
 Lcom/google/android/startop/iorap/IorapForwardingService$BinderConnectionHandler;
 Lcom/google/android/startop/iorap/IorapForwardingService;
+HPLandroid/hardware/health/V1_0/HealthInfo;-><init>()V
+HPLandroid/hardware/health/V2_0/DiskStats;-><init>()V
+HPLcom/android/server/-$$Lambda$GnssManagerService$a17GVVAgEci0VYD4EMvKwuPLhdQ;->onUidImportance(II)V
+HPLcom/android/server/-$$Lambda$GnssManagerService$mZAgy7PA5q3tB1aq7tHsX4xM14E;->run()V
+HPLcom/android/server/-$$Lambda$LocationManagerService$GVLGDgL1Vk3AKo-zMjRmo3-OLpQ;->run()V
+HPLcom/android/server/-$$Lambda$LocationManagerService$tHPgS5c0niUhGntiX8gOnWrZpg8;->onUidImportance(II)V
+HPLcom/android/server/accessibility/AccessibilityManagerService$Client;-><init>(Lcom/android/server/accessibility/AccessibilityManagerService;Landroid/view/accessibility/IAccessibilityManagerClient;ILcom/android/server/accessibility/AccessibilityUserState;)V
+HPLcom/android/server/accessibility/AccessibilitySecurityPolicy;->resolveCallingUserIdEnforcingPermissionsLocked(I)I
+HPLcom/android/server/am/-$$Lambda$OomAdjuster$OVkqAAacT5-taN3pgDzyZj3Ymvk;->handleMessage(Landroid/os/Message;)Z
+HPLcom/android/server/am/-$$Lambda$ProcessList$vtq7LF5jIHO4t5NE03c8g7BT7Jc;->run()V
+HPLcom/android/server/am/ActivityManagerService$2;->onActivityLaunched([BI)V
+HPLcom/android/server/am/ActivityManagerService$4;->isPackageForFilter(Ljava/lang/String;Landroid/content/IntentFilter;)Z
+HPLcom/android/server/am/ActivityManagerService$4;->newResult(Landroid/content/IntentFilter;II)Ljava/lang/Object;
+HPLcom/android/server/am/ActivityManagerService$PidMap;->put(Lcom/android/server/am/ProcessRecord;)V
+HPLcom/android/server/am/ActivityManagerService;->trimApplications(Ljava/lang/String;)V
+HPLcom/android/server/am/ActivityManagerService;->updateOomAdjLocked(Lcom/android/server/am/ProcessRecord;Ljava/lang/String;)V
+HPLcom/android/server/am/ActivityManagerService;->updateOomAdjLocked(Lcom/android/server/am/ProcessRecord;ZLjava/lang/String;)Z
+HPLcom/android/server/am/HostingRecord;->getName()Ljava/lang/String;
+HPLcom/android/server/am/HostingRecord;->getType()Ljava/lang/String;
+HPLcom/android/server/am/OomAdjuster;->setAttachingSchedGroupLocked(Lcom/android/server/am/ProcessRecord;)V
+HPLcom/android/server/am/ProcessList;->startProcessLocked(Lcom/android/server/am/HostingRecord;Ljava/lang/String;Lcom/android/server/am/ProcessRecord;I[IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)Z
+HPLcom/android/server/am/ProcessRecord;->computeOomAdjFromActivitiesIfNecessary(Lcom/android/server/am/OomAdjuster$ComputeOomAdjWindowCallback;IZIIIII)V
+HPLcom/android/server/appop/AppOpsService$FeatureOp;->started(JII)V
+HPLcom/android/server/appop/AppOpsService$FeatureOp;->updateProxyState(JILjava/lang/String;Ljava/lang/String;)V
+HPLcom/android/server/appop/AppOpsService;->noteOperation(IILjava/lang/String;Ljava/lang/String;)I
+HPLcom/android/server/appop/AudioRestrictionManager;->checkAudioOperation(IIILjava/lang/String;)I
+HPLcom/android/server/appprediction/-$$Lambda$AppPredictionManagerService$PredictionManagerServiceStub$4yDhFef-19aMlJ-Y7O6RdjSAvnk;-><init>(Landroid/app/prediction/AppPredictionSessionId;Landroid/app/prediction/AppTargetEvent;)V
+HPLcom/android/server/appprediction/-$$Lambda$RemoteAppPredictionService$qroIh2ewx0BLP-J9XIAX2CaX8J4;-><init>(Landroid/app/prediction/AppPredictionSessionId;Landroid/app/prediction/AppTargetEvent;)V
+HPLcom/android/server/appprediction/AppPredictionManagerService$PredictionManagerServiceStub;->runForUserLocked(Ljava/lang/String;Ljava/util/function/Consumer;)V
+HPLcom/android/server/appprediction/RemoteAppPredictionService;->lambda$notifyAppTargetEvent$1(Landroid/app/prediction/AppPredictionSessionId;Landroid/app/prediction/AppTargetEvent;Landroid/service/appprediction/IPredictionService;)V
+HPLcom/android/server/audio/SoundEffectsHelper$SfxHandler;->handleMessage(Landroid/os/Message;)V
+HPLcom/android/server/autofill/AutofillManagerService$AugmentedAutofillState;->injectAugmentedAutofillInfo(Landroid/content/AutofillOptions;ILjava/lang/String;)V
+HPLcom/android/server/autofill/AutofillManagerService$LocalService;->injectDisableAppInfo(Landroid/content/AutofillOptions;ILjava/lang/String;)V
+HPLcom/android/server/compat/CompatConfig;->getDisabledChanges(Landroid/content/pm/ApplicationInfo;)[J
+HPLcom/android/server/compat/CompatConfig;->get()Lcom/android/server/compat/CompatConfig;
+HPLcom/android/server/compat/PlatformCompat;->resetReporting(Landroid/content/pm/ApplicationInfo;)V
+HPLcom/android/server/contentcapture/ContentCaptureManagerService$GlobalContentCaptureOptions;->getOptions(ILjava/lang/String;)Landroid/content/ContentCaptureOptions;
+HPLcom/android/server/input/InputManagerService;->onPointerDownOutsideFocus(Landroid/os/IBinder;)V
+HPLcom/android/server/input/InputManagerService;->registerInputChannel(Landroid/view/InputChannel;)V
+HPLcom/android/server/inputmethod/InputMethodManagerService;->getActivityViewToScreenMatrixLocked(II)Landroid/graphics/Matrix;
+HPLcom/android/server/media/projection/MediaProjectionManagerService$1;->onForegroundActivitiesChanged(IIZ)V
+HPLcom/android/server/net/NetworkPolicyManagerService;->updateRulesForPowerRestrictionsULInner(II)I
+HPLcom/android/server/pm/permission/PermissionManagerService;->checkSingleUidPermissionInternal(ILjava/lang/String;)Z
+HPLcom/android/server/policy/PermissionPolicyService$Internal;->checkStartActivity(Landroid/content/Intent;ILjava/lang/String;)Z
+HPLcom/android/server/soundtrigger/SoundTriggerHelper;->computeRecognitionRequestedLocked()Z
+HPLcom/android/server/soundtrigger/SoundTriggerHelper;->isRecognitionAllowed()Z
+HPLcom/android/server/statusbar/-$$Lambda$StatusBarManagerService$dQguzfF4tEgBOj3Pr8MpGRN8HT0;->run()V
+HPLcom/android/server/statusbar/-$$Lambda$StatusBarManagerService$u5u_W7qW5cMnzk9Qhp_oReST4Dc;->run()V
+HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZZ)V
+HPLcom/android/server/usage/UsageStatsService;->reportEventOrAddToQueue(ILandroid/app/usage/UsageEvents$Event;)V
+HPLcom/android/server/usage/UserUsageStatsService;->checkAndGetTimeLocked()J
+HPLcom/android/server/wm/-$$Lambda$1Hjf_Nn5x4aIy9rIBTwVrtrzWFA;->apply(Ljava/lang/Object;)Ljava/lang/Object;
+HPLcom/android/server/wm/-$$Lambda$9vBfnQOmNnsc9WU80IIatZHQGKc;->get()Ljava/lang/Object;
+HPLcom/android/server/wm/-$$Lambda$ActivityRecord$BGON-BKR54yaxY8PHFXNV2xpxCM;->accept(Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$ActivityRecord$tt99EJHW_Nk5qgU9galJBIm5wXg;->run()V
+HPLcom/android/server/wm/-$$Lambda$ActivityRecord$YSVwd546vKWMiMYy7MFzg1qRiio;-><init>(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/-$$Lambda$ActivityRecord$YY5kCNb4uWg5W_2lbH3ZOqirP1g;->apply(Ljava/lang/Object;)Z
+HPLcom/android/server/wm/-$$Lambda$DisplayContent$a4EkCBfpZNIl1xfYgm2ktgndF8w;->apply(Ljava/lang/Object;)Z
+HPLcom/android/server/wm/-$$Lambda$DisplayContent$D0QJUvhaQkGgoMtOmjw5foY9F8M;->accept(Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayContent$eJsj3GR1HdCnOJrZ8_oaLP52jg0;->accept(Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayContent$SeHNTr4WUVpGmQniHULUi1ST7k8;->accept(Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayContent$sYPOy6TL-QiWuU_jcEHYn4HeFnQ;->accept(Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$3MnyIKSHFLqhfUifWEQPNp_-J6A;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$IOyP8YVRG92tn9u1muYWZgBbgc0;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$J8sIwXJvltUaPM3jEGO948Bx9ig;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$LkHee4mchNXMwNt7HLgsMzHofeE;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$QDPgWUhyEOraWnf6a-u4mTBttdw;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$qQY9m_Itua9TDy-Nk3zzDxvjEwE;->run()V
+HPLcom/android/server/wm/-$$Lambda$LaunchObserverRegistryImpl$QcawcFcJtEX4EhYptq_Vb4j368Y;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$LaunchObserverRegistryImpl$veRn_GhgLZLlOHOJ0ZYT6KcfYqo;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$qMFJUmfG50ZSjk7Tac67xBia0d4;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$RemoteAnimationController$74uuXaM2TqjkzYi0b8LqJdbycxA;-><init>(Lcom/android/server/wm/RemoteAnimationController;[Landroid/view/RemoteAnimationTarget;[Landroid/view/RemoteAnimationTarget;)V
+HPLcom/android/server/wm/-$$Lambda$RemoteAnimationController$74uuXaM2TqjkzYi0b8LqJdbycxA;->run()V
+HPLcom/android/server/wm/-$$Lambda$RootWindowContainer$7XcqfZjQLAbjpIyed3iDnVtZro4;->accept(Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$SurfaceAnimator$M9kRDTUpVS03LTqe-QLQz3DnMhk;-><init>(Lcom/android/server/wm/SurfaceAnimator;Lcom/android/server/wm/AnimationAdapter;Ljava/lang/Runnable;)V
+HPLcom/android/server/wm/-$$Lambda$SurfaceAnimator$M9kRDTUpVS03LTqe-QLQz3DnMhk;->run()V
+HPLcom/android/server/wm/-$$Lambda$TaskChangeNotificationController$Kz-Od_gLhLbMtGka4r78W0Gmzgo;->accept(Landroid/app/ITaskStackListener;Landroid/os/Message;)V
+HPLcom/android/server/wm/-$$Lambda$TaskPersister$xdLXwftXa6l84QTg1zpxMnmtQ0g;-><init>(Lcom/android/server/wm/TaskRecord;)V
+HPLcom/android/server/wm/-$$Lambda$uwO6wQlqU3CG7OTdH7NBCKnHs64;->accept(Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$UZl9uqUNteVgplGGEK6TMzf-7zk;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$VY87MmFWaCLMkNa2qHGaPrThyrI;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/ActivityDisplay;->alwaysCreateStack(II)Z
+HPLcom/android/server/wm/ActivityDisplay;->pauseBackStacks(ZLcom/android/server/wm/ActivityRecord;)Z
+HPLcom/android/server/wm/ActivityDisplay;->updateDisplayOverrideConfigurationLocked(Landroid/content/res/Configuration;Lcom/android/server/wm/ActivityRecord;ZLcom/android/server/wm/ActivityTaskManagerService$UpdateConfigurationResult;)Z
+HPLcom/android/server/wm/ActivityMetricsLogger;->reset(ZLcom/android/server/wm/ActivityMetricsLogger$WindowingModeTransitionInfo;Ljava/lang/String;J)V
+HPLcom/android/server/wm/ActivityRecord$AddStartingWindow;-><init>(Lcom/android/server/wm/ActivityRecord;Lcom/android/server/wm/ActivityRecord$1;)V
+HPLcom/android/server/wm/ActivityRecord$AddStartingWindow;->run()V
+HPLcom/android/server/wm/ActivityRecord$Token;-><init>(Landroid/content/Intent;)V
+HPLcom/android/server/wm/ActivityRecord;->addWindow(Lcom/android/server/wm/WindowState;)V
+HPLcom/android/server/wm/ActivityRecord;->adjustPinnedStackAndInitChangeTransitionIfNeeded(II)V
+HPLcom/android/server/wm/ActivityRecord;->asActivityRecord()Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/ActivityRecord;->checkAppWindowsReadyToShow()V
+HPLcom/android/server/wm/ActivityRecord;->checkCompleteDeferredRemoval()Z
+HPLcom/android/server/wm/ActivityRecord;->clearAnimatingFlags()V
+HPLcom/android/server/wm/ActivityRecord;->commitVisibility(Landroid/view/WindowManager$LayoutParams;ZIZZ)Z
+HPLcom/android/server/wm/ActivityRecord;->computeBounds(Landroid/graphics/Rect;Landroid/graphics/Rect;)V
+HPLcom/android/server/wm/ActivityRecord;->containsDismissKeyguardWindow()Z
+HPLcom/android/server/wm/ActivityRecord;->containsShowWhenLockedWindow()Z
+HPLcom/android/server/wm/ActivityRecord;->destroySurfaces(Z)V
+HPLcom/android/server/wm/ActivityRecord;->detachChildren()V
+HPLcom/android/server/wm/ActivityRecord;->fillsParent()Z
+HPLcom/android/server/wm/ActivityRecord;->findMainWindow()Lcom/android/server/wm/WindowState;
+HPLcom/android/server/wm/ActivityRecord;->findMainWindow(Z)Lcom/android/server/wm/WindowState;
+HPLcom/android/server/wm/ActivityRecord;->forAllWindows(Lcom/android/internal/util/ToBooleanFunction;Z)Z
+HPLcom/android/server/wm/ActivityRecord;->getAnimationLeashParent()Landroid/view/SurfaceControl;
+HPLcom/android/server/wm/ActivityRecord;->getDisplayedBounds()Landroid/graphics/Rect;
+HPLcom/android/server/wm/ActivityRecord;->getOrientation(I)I
+HPLcom/android/server/wm/ActivityRecord;->getStartingWindowType(ZZZZZZLandroid/app/ActivityManager$TaskSnapshot;)I
+HPLcom/android/server/wm/ActivityRecord;->getTask()Lcom/android/server/wm/Task;
+HPLcom/android/server/wm/ActivityRecord;->handleAlreadyVisible()V
+HPLcom/android/server/wm/ActivityRecord;->isAppAnimating()Z
+HPLcom/android/server/wm/ActivityRecord;->isClientHidden()Z
+HPLcom/android/server/wm/ActivityRecord;->isFirstChildWindowGreaterThanSecond(Lcom/android/server/wm/WindowState;Lcom/android/server/wm/WindowState;)Z
+HPLcom/android/server/wm/ActivityRecord;->isResolverOrDelegateActivity()Z
+HPLcom/android/server/wm/ActivityRecord;->isSelfAnimating()Z
+HPLcom/android/server/wm/ActivityRecord;->isVisible()Z
+HPLcom/android/server/wm/ActivityRecord;->isWaitingForTransitionStart()Z
+HPLcom/android/server/wm/ActivityRecord;->layoutLetterbox(Lcom/android/server/wm/WindowState;)V
+HPLcom/android/server/wm/ActivityRecord;->makeInvisible()V
+HPLcom/android/server/wm/ActivityRecord;->needsZBoost()Z
+HPLcom/android/server/wm/ActivityRecord;->onAnimationLeashCreated(Landroid/view/SurfaceControl$Transaction;Landroid/view/SurfaceControl;)V
+HPLcom/android/server/wm/ActivityRecord;->onAnimationLeashLost(Landroid/view/SurfaceControl$Transaction;)V
+HPLcom/android/server/wm/ActivityRecord;->onAppTransitionDone()V
+HPLcom/android/server/wm/ActivityRecord;->onDisplayChanged(Lcom/android/server/wm/DisplayContent;)V
+HPLcom/android/server/wm/ActivityRecord;->onParentChanged()V
+HPLcom/android/server/wm/ActivityRecord;->postWindowRemoveStartingWindowCleanup(Lcom/android/server/wm/WindowState;)V
+HPLcom/android/server/wm/ActivityRecord;->prepareSurfaces()V
+HPLcom/android/server/wm/ActivityRecord;->removeChild(Lcom/android/server/wm/WindowContainer;)V
+HPLcom/android/server/wm/ActivityRecord;->removeStartingWindow()V
+HPLcom/android/server/wm/ActivityRecord;->scheduleAddStartingWindow()V
+HPLcom/android/server/wm/ActivityRecord;->scheduleTopResumedActivityChanged(Z)Z
+HPLcom/android/server/wm/ActivityRecord;->setHidden(Z)V
+HPLcom/android/server/wm/ActivityRecord;->setLayer(Landroid/view/SurfaceControl$Transaction;I)V
+HPLcom/android/server/wm/ActivityRecord;->setVisibility(ZZ)V
+HPLcom/android/server/wm/ActivityRecord;->shouldDeferAnimationFinish(Ljava/lang/Runnable;)Z
+HPLcom/android/server/wm/ActivityRecord;->stopFreezingScreen(ZZ)V
+HPLcom/android/server/wm/ActivityRecord;->stopIfPossible()V
+HPLcom/android/server/wm/ActivityRecord;->transferStartingWindow(Landroid/os/IBinder;)Z
+HPLcom/android/server/wm/ActivityRecord;->updateAllDrawn()V
+HPLcom/android/server/wm/ActivityRecord;->updateDrawnWindowStates(Lcom/android/server/wm/WindowState;)Z
+HPLcom/android/server/wm/ActivityRecord;->updateLetterboxSurface(Lcom/android/server/wm/WindowState;)V
+HPLcom/android/server/wm/ActivityRecord;->updateReportedVisibilityLocked()V
+HPLcom/android/server/wm/ActivityRecord;->windowsAreFocusable()Z
+HPLcom/android/server/wm/ActivityRecord;->writeToProto(Landroid/util/proto/ProtoOutputStream;JI)V
+HPLcom/android/server/wm/ActivityStack;->removeLaunchTickMessages()V
+HPLcom/android/server/wm/ActivityStack;->removeStopTimeoutForActivity(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/ActivityStack;->scheduleLaunchTickForActivity(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/ActivityStack;->topTask()Lcom/android/server/wm/TaskRecord;
+HPLcom/android/server/wm/ActivityStarter;->startActivity(Lcom/android/server/wm/ActivityRecord;Lcom/android/server/wm/ActivityRecord;Landroid/service/voice/IVoiceInteractionSession;Lcom/android/internal/app/IVoiceInteractor;IZLandroid/app/ActivityOptions;Lcom/android/server/wm/TaskRecord;[Lcom/android/server/wm/ActivityRecord;Z)I
+HPLcom/android/server/wm/ActivityTaskManagerService;->addWindowLayoutReasons(I)V
+HPLcom/android/server/wm/ActivityTaskManagerService;->continueWindowLayout()V
+HPLcom/android/server/wm/ActivityTaskManagerService;->deferWindowLayout()V
+HPLcom/android/server/wm/ActivityTaskManagerService;->getPermissionPolicyInternal()Lcom/android/server/policy/PermissionPolicyInternal;
+HPLcom/android/server/wm/ActivityTaskManagerService;->isCrossUserAllowed(II)Z
+HPLcom/android/server/wm/ActivityTaskManagerService;->startProcessAsync(Lcom/android/server/wm/ActivityRecord;ZZLjava/lang/String;)V
+HPLcom/android/server/wm/AppTransitionController;->findAnimLayoutParamsToken(ILandroid/util/ArraySet;)Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/AppTransitionController;->getRemoteAnimationOverride(Lcom/android/server/wm/ActivityRecord;ILandroid/util/ArraySet;)Landroid/view/RemoteAnimationAdapter;
+HPLcom/android/server/wm/AppTransitionController;->getTopApp(Landroid/util/ArraySet;Z)Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/AppTransition;->getAnimationStyleResId(Landroid/view/WindowManager$LayoutParams;)I
+HPLcom/android/server/wm/AppTransition;->goodToGo(ILcom/android/server/wm/ActivityRecord;Landroid/util/ArraySet;)I
+HPLcom/android/server/wm/AppTransition;->setLastAppTransition(ILcom/android/server/wm/ActivityRecord;Lcom/android/server/wm/ActivityRecord;Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/ConfigurationContainer;->hasChild()Z
+HPLcom/android/server/wm/ConfigurationContainer;->setWindowingMode(I)V
+HPLcom/android/server/wm/DisplayContent;->computeImeTargetIfNeeded(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/DisplayContent;->getInsetsPolicy()Lcom/android/server/wm/InsetsPolicy;
+HPLcom/android/server/wm/DisplayContent;->getRotationAnimation()Lcom/android/server/wm/ScreenRotationAnimation;
+HPLcom/android/server/wm/DisplayContent;->performLayoutNoTrace(ZZ)V
+HPLcom/android/server/wm/DisplayContent;->setFocusedApp(Lcom/android/server/wm/ActivityRecord;)Z
+HPLcom/android/server/wm/DisplayContent;->updateDisplayAndOrientation(ILandroid/content/res/Configuration;)Landroid/view/DisplayInfo;
+HPLcom/android/server/wm/DisplayContent;->updateFocusedWindowLocked(IZI)Z
+HPLcom/android/server/wm/DisplayContent;->updateOrientation(Landroid/content/res/Configuration;Landroid/os/IBinder;Z)Landroid/content/res/Configuration;
+HPLcom/android/server/wm/DisplayContent;->updateOrientation()Z
+HPLcom/android/server/wm/DisplayPolicy;->addWindowLw(Lcom/android/server/wm/WindowState;Landroid/view/WindowManager$LayoutParams;)V
+HPLcom/android/server/wm/DisplayPolicy;->adjustWindowParamsLw(Lcom/android/server/wm/WindowState;Landroid/view/WindowManager$LayoutParams;II)V
+HPLcom/android/server/wm/DisplayPolicy;->configureNavBarOpacity(IZZZZZ)I
+HPLcom/android/server/wm/DisplayPolicy;->drawsBarBackground(ILcom/android/server/wm/WindowState;Lcom/android/server/wm/BarController;I)Z
+HPLcom/android/server/wm/DisplayPolicy;->drawsNavigationBarBackground(ILcom/android/server/wm/WindowState;)Z
+HPLcom/android/server/wm/DisplayPolicy;->getRefreshRatePolicy()Lcom/android/server/wm/RefreshRatePolicy;
+HPLcom/android/server/wm/DisplayPolicy;->isWindowExcludedFromContent(Lcom/android/server/wm/WindowState;)Z
+HPLcom/android/server/wm/DisplayPolicy;->selectAnimation(Lcom/android/server/wm/WindowState;I)I
+HPLcom/android/server/wm/DisplayPolicy;->updateConfigurationAndScreenSizeDependentBehaviors()V
+HPLcom/android/server/wm/DisplayRotation;->getRotation()I
+HPLcom/android/server/wm/DisplayRotation;->markForSeamlessRotation(Lcom/android/server/wm/WindowState;Z)V
+HPLcom/android/server/wm/ImeInsetsSourceProvider;->onPostInsetsDispatched()V
+HPLcom/android/server/wm/ImeInsetsSourceProvider;->onPostLayout()V
+HPLcom/android/server/wm/InputManagerCallback;->notifyFocusChanged(Landroid/os/IBinder;Landroid/os/IBinder;)Z
+HPLcom/android/server/wm/InputMonitor$UpdateInputWindows;->run()V
+HPLcom/android/server/wm/InsetsPolicy;->areSystemBarsForciblyVisible()Z
+HPLcom/android/server/wm/InsetsPolicy;->isNavBarForciblyVisible()Z
+HPLcom/android/server/wm/InsetsPolicy;->isStatusBarForciblyVisible()Z
+HPLcom/android/server/wm/InsetsStateController;->getImeSourceProvider()Lcom/android/server/wm/ImeInsetsSourceProvider;
+HPLcom/android/server/wm/InsetsStateController;->onBarControlTargetChanged(Lcom/android/server/wm/InsetsControlTarget;Lcom/android/server/wm/InsetsControlTarget;)V
+HPLcom/android/server/wm/LaunchObserverRegistryImpl;->onIntentStarted(Landroid/content/Intent;J)V
+HPLcom/android/server/wm/PinnedStackController;->resetReentrySnapFraction(Landroid/content/ComponentName;)V
+HPLcom/android/server/wm/RecentTasks;->isInVisibleRange(Lcom/android/server/wm/TaskRecord;IIZ)Z
+HPLcom/android/server/wm/RefreshRatePolicy;->getPreferredModeId(Lcom/android/server/wm/WindowState;)I
+HPLcom/android/server/wm/RemoteAnimationController$FinishedCallback;-><init>(Lcom/android/server/wm/RemoteAnimationController;)V
+HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationRecord;->getMode()I
+HPLcom/android/server/wm/RemoteAnimationController;->createAppAnimations()[Landroid/view/RemoteAnimationTarget;
+HPLcom/android/server/wm/RemoteAnimationController;->createWallpaperAnimations()[Landroid/view/RemoteAnimationTarget;
+HPLcom/android/server/wm/RemoteAnimationController;-><init>(Lcom/android/server/wm/WindowManagerService;Landroid/view/RemoteAnimationAdapter;Landroid/os/Handler;)V
+HPLcom/android/server/wm/RemoteAnimationController;->releaseFinishedCallback()V
+HPLcom/android/server/wm/RemoteAnimationController;->setRunningRemoteAnimation(Z)V
+HPLcom/android/server/wm/RemoteAnimationController;->unlinkToDeathOfRunner()V
+HPLcom/android/server/wm/RootActivityContainer;->getLaunchStack(Lcom/android/server/wm/ActivityRecord;Landroid/app/ActivityOptions;Lcom/android/server/wm/TaskRecord;ZLcom/android/server/wm/LaunchParamsController$LaunchParams;II)Lcom/android/server/wm/ActivityStack;
+HPLcom/android/server/wm/RootActivityContainer;->getRunningTasks(ILjava/util/List;IIIZZLandroid/util/ArraySet;)V
+HPLcom/android/server/wm/RootWindowContainer;->getActivityRecord(Landroid/os/IBinder;)Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/Session;->finishDrawing(Landroid/view/IWindow;Landroid/view/SurfaceControl$Transaction;)V
+HPLcom/android/server/wm/Session;->reportSystemGestureExclusionChanged(Landroid/view/IWindow;Ljava/util/List;)V
+HPLcom/android/server/wm/StartingData;-><init>(Lcom/android/server/wm/WindowManagerService;)V
+HPLcom/android/server/wm/SurfaceAnimationRunner;->startPendingAnimationsLocked()V
+HPLcom/android/server/wm/SurfaceAnimator$Animatable;->shouldDeferAnimationFinish(Ljava/lang/Runnable;)Z
+HPLcom/android/server/wm/SurfaceAnimator;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/Task;->addChild(Lcom/android/server/wm/ActivityRecord;I)V
+HPLcom/android/server/wm/TaskChangeNotificationController;->notifyTaskListUpdated()V
+HPLcom/android/server/wm/Task;->getTopVisibleActivity()Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/TaskLaunchParamsModifier;->canInheritWindowingModeFromSource(Lcom/android/server/wm/ActivityDisplay;Lcom/android/server/wm/ActivityRecord;)Z
+HPLcom/android/server/wm/Task;->onConfigurationChanged(Landroid/content/res/Configuration;Z)V
+HPLcom/android/server/wm/TaskPersister$TaskWriteQueueItem;-><init>(Lcom/android/server/wm/TaskRecord;Lcom/android/server/wm/ActivityTaskManagerService;)V
+HPLcom/android/server/wm/Task;->positionChildAt(Lcom/android/server/wm/ActivityRecord;I)V
+HPLcom/android/server/wm/TaskRecord;->computeFullscreenBounds(Landroid/graphics/Rect;Lcom/android/server/wm/ActivityRecord;Landroid/graphics/Rect;I)V
+HPLcom/android/server/wm/TaskRecord;->findRootIndex(Z)I
+HPLcom/android/server/wm/TaskRecord;-><init>(Lcom/android/server/wm/ActivityTaskManagerService;ILandroid/content/pm/ActivityInfo;Landroid/content/Intent;Landroid/service/voice/IVoiceInteractionSession;Lcom/android/internal/app/IVoiceInteractor;Landroid/app/ActivityManager$TaskDescription;)V
+HPLcom/android/server/wm/UnknownAppVisibilityController;->notifyAppResumedFinished(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/UnknownAppVisibilityController;->notifyRelayouted(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->getLeashFinishedCallback()Lcom/android/server/wm/SurfaceAnimator$OnAnimationFinishedCallback;
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->lambda$startWallpaperAnimations$0(JJLjava/util/function/Consumer;Ljava/util/ArrayList;Ljava/util/ArrayList;Lcom/android/server/wm/WallpaperWindowToken;)V
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->startAnimation(Landroid/view/SurfaceControl;Landroid/view/SurfaceControl$Transaction;Lcom/android/server/wm/SurfaceAnimator$OnAnimationFinishedCallback;)V
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->startWallpaperAnimations(Lcom/android/server/wm/WindowManagerService;JJLjava/util/function/Consumer;Ljava/util/ArrayList;)[Landroid/view/RemoteAnimationTarget;
+HPLcom/android/server/wm/WindowAnimator;->cancelAnimation()V
+HPLcom/android/server/wm/WindowContainer;->onAnimationLeashLost(Landroid/view/SurfaceControl$Transaction;)V
+HPLcom/android/server/wm/WindowContainer;->onParentChanged(Lcom/android/server/wm/WindowContainer$PreAssignChildLayersCallback;)V
+HPLcom/android/server/wm/WindowManagerService$LocalService;->isStackVisibleLw(I)Z
+HPLcom/android/server/wm/WindowManagerService;->prepareNoneTransitionForRelaunching(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/WindowManagerService;->prepareWindowReplacementTransition(Lcom/android/server/wm/ActivityRecord;)Z
+HPLcom/android/server/wm/WindowProcessController;->createProfilerInfoIfNeeded()Landroid/app/ProfilerInfo;
+HPLcom/android/server/wm/WindowProcessControllerMap;->put(ILcom/android/server/wm/WindowProcessController;)V
+HPLcom/android/server/wm/WindowProcessController;->onStartActivity(ILandroid/content/pm/ActivityInfo;)V
+HPLcom/android/server/wm/WindowProcessController;->setLastActivityLaunchTime(J)V
+HPLcom/android/server/wm/WindowState$UpdateReportedVisibilityResults;-><init>()V
+HPLcom/android/server/wm/WindowState;->getLastReportedConfiguration()Landroid/content/res/Configuration;
+HPLcom/android/server/wm/WindowState;->getSurfaceTouchableRegion(Landroid/view/InputWindowHandle;I)I
+HPLcom/android/server/wm/WindowState;->logExclusionRestrictions(I)V
+HPLcom/android/server/wm/WindowState;->onAnimationLeashLost(Landroid/view/SurfaceControl$Transaction;)V
+HPLcom/android/server/wm/WindowState;->relayoutVisibleWindow(II)I
+HPLcom/android/server/wm/WindowState;->updateLocationInParentDisplayIfNeeded()V
+HPLcom/android/server/wm/WindowSurfaceController;-><init>(Ljava/lang/String;IIIILcom/android/server/wm/WindowStateAnimator;II)V
+HPLcom/android/server/wm/WindowSurfacePlacer$Traverser;->run()V
+HPLcom/android/server/wm/WindowToken;-><init>(Lcom/android/server/wm/WindowManagerService;Landroid/os/IBinder;IZLcom/android/server/wm/DisplayContent;Z)V
+HPLcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;->onActivityLaunched([BI)V
+HPLcom/android/server/accessibility/AccessibilityManagerService;->computeRelevantEventTypesLocked(Lcom/android/server/accessibility/AccessibilityUserState;Lcom/android/server/accessibility/AccessibilityManagerService$Client;)I
+HPLcom/android/server/accessibility/AccessibilitySecurityPolicy;->resolveProfileParentLocked(I)I
+HPLcom/android/server/am/-$$Lambda$ProcessList$vtq7LF5jIHO4t5NE03c8g7BT7Jc;-><init>(Lcom/android/server/am/ProcessList;Lcom/android/server/am/ProcessRecord;Ljava/lang/String;[IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V
+HPLcom/android/server/am/ActivityManagerService$2;->onActivityLaunchFinished([BJ)V
+HPLcom/android/server/am/ActivityManagerService$2;->onIntentStarted(Landroid/content/Intent;J)V
+HPLcom/android/server/am/ActivityManagerService$4;->isPackageForFilter(Ljava/lang/String;Lcom/android/server/am/BroadcastFilter;)Z
+HPLcom/android/server/am/ActivityManagerService$4;->newResult(Lcom/android/server/am/BroadcastFilter;II)Lcom/android/server/am/BroadcastFilter;
+HPLcom/android/server/am/ActivityManagerService$LocalService;->startProcess(Ljava/lang/String;Landroid/content/pm/ApplicationInfo;ZZLjava/lang/String;Landroid/content/ComponentName;)V
+HPLcom/android/server/am/ActivityManagerService;->trimApplicationsLocked(Ljava/lang/String;)V
+HPLcom/android/server/am/ProcessList;->lambda$startProcessLocked$0$ProcessList(Lcom/android/server/am/ProcessRecord;Ljava/lang/String;[IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V
+HPLcom/android/server/am/ProcessRecord;->onStartActivity(IZLjava/lang/String;J)V
+HPLcom/android/server/am/ProcessRecord;->setRunningRemoteAnimation(Z)V
+HPLcom/android/server/am/ProcessRecord;->setStartParams(ILcom/android/server/am/HostingRecord;Ljava/lang/String;J)V
+HPLcom/android/server/appprediction/RemoteAppPredictionService;->lambda$notifyAppTargetEvent$1(Landroid/app/prediction/AppPredictionSessionId;Landroid/app/prediction/AppTargetEvent;Landroid/service/appprediction/IPredictionService;)V
+HPLcom/android/server/audio/SoundEffectsHelper$SfxHandler$1;-><init>(Lcom/android/server/audio/SoundEffectsHelper$SfxHandler;Landroid/os/Message;)V
+HPLcom/android/server/audio/SoundEffectsHelper;->onLoadSoundEffects(Lcom/android/server/audio/SoundEffectsHelper$OnEffectsLoadCompleteHandler;)V
+HPLcom/android/server/autofill/AutofillManagerServiceImpl;->getAppDisabledActivitiesLocked(Ljava/lang/String;)Landroid/util/ArrayMap;
+HPLcom/android/server/compat/CompatChange;->isEnabled(Landroid/content/pm/ApplicationInfo;)Z
+HPLcom/android/server/GnssManagerService;->onForegroundChanged(IZ)V
+HPLcom/android/server/LocationManagerService;->lambda$initializeLocked$4$LocationManagerService(II)V
+HPLcom/android/server/LocationManagerService;->lambda$initializeLocked$5$LocationManagerService(II)V
+HPLcom/android/server/policy/PermissionPolicyService$Internal;->isActionRemovedForCallingPackage(Landroid/content/Intent;ILjava/lang/String;)Z
+HPLcom/android/server/statusbar/-$$Lambda$StatusBarManagerService$dQguzfF4tEgBOj3Pr8MpGRN8HT0;-><init>(Lcom/android/server/statusbar/StatusBarManagerService;ILandroid/os/IBinder;IIZZ)V
+HPLcom/android/server/statusbar/StatusBarManagerService;->lambda$setImeWindowStatus$2$StatusBarManagerService(ILandroid/os/IBinder;IIZZ)V
+HPLcom/android/server/statusbar/StatusBarManagerService;->lambda$updateUiVisibilityLocked$3$StatusBarManagerService(IIIIILandroid/graphics/Rect;Landroid/graphics/Rect;Z)V
+HPLcom/android/server/wm/-$$Lambda$ActivityRecord$tt99EJHW_Nk5qgU9galJBIm5wXg;-><init>(Lcom/android/server/policy/WindowManagerPolicy$StartingSurface;)V
+HPLcom/android/server/wm/-$$Lambda$RemoteAnimationController$dP8qDptNigoqhzVtIudsX5naGu4;-><init>(Lcom/android/server/wm/RemoteAnimationController;)V
+HPLcom/android/server/wm/-$$Lambda$RemoteAnimationController$uQS8vaPKQ-E3x_9G8NCxPQmw1fw;-><init>(Lcom/android/server/wm/RemoteAnimationController;)V
+HPLcom/android/server/wm/ActivityRecord;->addToStopping(ZZLjava/lang/String;)V
+HPLcom/android/server/wm/ActivityRecord;->allDrawnStatesConsidered()Z
+HPLcom/android/server/wm/ActivityRecord;->applyAnimationLocked(Landroid/view/WindowManager$LayoutParams;IZZ)Z
+HPLcom/android/server/wm/ActivityRecord;->checkKeyguardFlagsChanged()V
+HPLcom/android/server/wm/ActivityRecord;->lambda$removeStartingWindow$3(Lcom/android/server/policy/WindowManagerPolicy$StartingSurface;)V
+HPLcom/android/server/wm/ActivityRecord;->shouldStartChangeTransition(II)Z
+HPLcom/android/server/wm/ActivityRecord;->transferStartingWindowFromHiddenAboveTokenIfNeeded()V
+HPLcom/android/server/wm/ActivityStack;->scheduleStopTimeoutForActivity(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/ActivityStack;->startPausingLocked(ZZLcom/android/server/wm/ActivityRecord;)Z
+HPLcom/android/server/wm/ActivityStackSupervisor;->getRunningTasks()Lcom/android/server/wm/RunningTasks;
+HPLcom/android/server/wm/ActivityStackSupervisor;->getSystemChooserActivity()Landroid/content/ComponentName;
+HPLcom/android/server/wm/ActivityStarter;->handleStartResult(Lcom/android/server/wm/ActivityRecord;I)Lcom/android/server/wm/ActivityStack;
+HPLcom/android/server/wm/ActivityStarter;->startActivityInner(Lcom/android/server/wm/ActivityRecord;Lcom/android/server/wm/ActivityRecord;Landroid/service/voice/IVoiceInteractionSession;Lcom/android/internal/app/IVoiceInteractor;IZLandroid/app/ActivityOptions;Lcom/android/server/wm/TaskRecord;[Lcom/android/server/wm/ActivityRecord;Z)I
+HPLcom/android/server/wm/ActivityTaskManagerService;->ensureConfigAndVisibilityAfterUpdate(Lcom/android/server/wm/ActivityRecord;I)Z
+HPLcom/android/server/wm/AnimatingActivityRegistry;->endDeferringFinished()V
+HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/AppTransitionController;->lookForHighestTokenWithFilter(Landroid/util/ArraySet;Landroid/util/ArraySet;Landroid/util/ArraySet;Ljava/util/function/Predicate;)Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/DisplayContent;->findFocusedWindowIfNeeded(I)Lcom/android/server/wm/WindowState;
+HPLcom/android/server/wm/DisplayContent;->getActivityRecord(Landroid/os/IBinder;)Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/DisplayContent;->getParentWindow()Lcom/android/server/wm/WindowState;
+HPLcom/android/server/wm/DisplayContent;->lambda$new$1$DisplayContent(Lcom/android/server/wm/WindowState;)V
+HPLcom/android/server/wm/DisplayContent;->lambda$new$2$DisplayContent(Lcom/android/server/wm/WindowState;)Z
+HPLcom/android/server/wm/DisplayContent;->lambda$new$3$DisplayContent(Lcom/android/server/wm/WindowState;)V
+HPLcom/android/server/wm/DisplayContent;->lambda$updateSystemUiVisibility$20(IILcom/android/server/wm/WindowState;)V
+HPLcom/android/server/wm/DisplayContent;->logsGestureExclusionRestrictions(Lcom/android/server/wm/WindowState;)Z
+HPLcom/android/server/wm/DisplayPolicy;->getCurrentUserResources()Landroid/content/res/Resources;
+HPLcom/android/server/wm/DisplayPolicy;->getStatusBar()Lcom/android/server/wm/WindowState;
+HPLcom/android/server/wm/DisplayPolicy;->lambda$addWindowLw$3$DisplayPolicy(Lcom/android/server/wm/DisplayFrames;Lcom/android/server/wm/WindowState;Landroid/graphics/Rect;)V
+HPLcom/android/server/wm/DisplayPolicy;->lambda$addWindowLw$4$DisplayPolicy(Lcom/android/server/wm/DisplayFrames;Lcom/android/server/wm/WindowState;Landroid/graphics/Rect;)V
+HPLcom/android/server/wm/DisplayPolicy;->lambda$addWindowLw$5$DisplayPolicy(Lcom/android/server/wm/DisplayFrames;Lcom/android/server/wm/WindowState;Landroid/graphics/Rect;)V
+HPLcom/android/server/wm/DisplayPolicy;->lambda$addWindowLw$6$DisplayPolicy(Lcom/android/server/wm/DisplayFrames;Lcom/android/server/wm/WindowState;Landroid/graphics/Rect;)V
+HPLcom/android/server/wm/DisplayPolicy;->lambda$addWindowLw$7$DisplayPolicy(Lcom/android/server/wm/DisplayFrames;Lcom/android/server/wm/WindowState;Landroid/graphics/Rect;)V
+HPLcom/android/server/wm/DisplayPolicy;->lambda$updateSystemUiVisibilityLw$10$DisplayPolicy(IIILandroid/graphics/Rect;Landroid/graphics/Rect;ZLcom/android/server/wm/WindowState;Z)V
+HPLcom/android/server/wm/DisplayRotation;->updateOrientation(IZ)Z
+HPLcom/android/server/wm/DisplayRotation;->updateUserDependentConfiguration(Landroid/content/res/Resources;)V
+HPLcom/android/server/wm/HighRefreshRateBlacklist;->isBlacklisted(Ljava/lang/String;)Z
+HPLcom/android/server/wm/InputManagerCallback;->dispatchPointerCaptureChanged(Landroid/view/IWindow;Z)Z
+HPLcom/android/server/wm/InputManagerCallback;->onPointerDownOutsideFocus(Landroid/os/IBinder;)V
+HPLcom/android/server/wm/InputMonitor;->layoutInputConsumers(II)V
+HPLcom/android/server/wm/InputMonitor;->setFocusedAppLw(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/InsetsStateController;->onControlChanged(ILcom/android/server/wm/InsetsControlTarget;)V
+HPLcom/android/server/wm/LaunchObserverRegistryImpl;->onActivityLaunchFinished([BJ)V
+HPLcom/android/server/wm/RemoteAnimationController$FinishedCallback;->release()V
+HPLcom/android/server/wm/RemoteAnimationController;->lambda$goodToGo$1$RemoteAnimationController([Landroid/view/RemoteAnimationTarget;[Landroid/view/RemoteAnimationTarget;)V
+HPLcom/android/server/wm/RunningTasks;->getTasks(ILjava/util/List;IILjava/util/ArrayList;IZZLandroid/util/ArraySet;)V
+HPLcom/android/server/wm/SplashScreenStartingData;->createStartingSurface(Lcom/android/server/wm/ActivityRecord;)Lcom/android/server/policy/WindowManagerPolicy$StartingSurface;
+HPLcom/android/server/wm/SurfaceAnimator;->lambda$getFinishedCallback$0$SurfaceAnimator(Lcom/android/server/wm/AnimationAdapter;Ljava/lang/Runnable;)V
+HPLcom/android/server/wm/Task;->positionChildAt(ILcom/android/server/wm/ActivityRecord;Z)V
+HPLcom/android/server/wm/TaskRecord;->handlesOrientationChangeFromDescendant()Z
+HPLcom/android/server/wm/TaskRecord;-><init>(Lcom/android/server/wm/ActivityTaskManagerService;ILandroid/content/Intent;Landroid/content/Intent;Ljava/lang/String;Ljava/lang/String;Landroid/content/ComponentName;Landroid/content/ComponentName;ZZZIILjava/lang/String;Ljava/util/ArrayList;JZLandroid/app/ActivityManager$TaskDescription;IIIIILjava/lang/String;IZZZIILandroid/content/pm/ActivityInfo;Landroid/service/voice/IVoiceInteractionSession;Lcom/android/internal/app/IVoiceInteractor;)V
+HPLcom/android/server/wm/TaskStack;->getAnimatingActivityRegistry()Lcom/android/server/wm/AnimatingActivityRegistry;
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->startWallpaperAnimations(Lcom/android/server/wm/WindowManagerService;JJLjava/util/function/Consumer;Ljava/util/ArrayList;)[Landroid/view/RemoteAnimationTarget;
+HPLcom/android/server/wm/WindowContainer$ForAllWindowsConsumerWrapper;-><init>(Lcom/android/server/wm/WindowContainer;Lcom/android/server/wm/WindowContainer$1;)V
+HPLcom/android/server/wm/WindowManagerService;->finishDrawingWindow(Lcom/android/server/wm/Session;Landroid/view/IWindow;Landroid/view/SurfaceControl$Transaction;)V
+HPLcom/android/server/wm/WindowManagerService;->reportSystemGestureExclusionChanged(Lcom/android/server/wm/Session;Landroid/view/IWindow;Ljava/util/List;)V
+HPLcom/android/server/wm/WindowProcessController;->setRunningRemoteAnimation(Z)V
+HPLcom/android/server/wm/WindowProcessController;->shouldSetProfileProc()Z
+HPLcom/android/server/wm/WindowState;->onMergedOverrideConfigurationChanged()V
+HPLcom/android/server/wm/WindowSurfacePlacer;->continueLayout(Z)V
+HPLcom/android/server/wm/WindowSurfacePlacer;->isLayoutDeferred()Z
+HPLcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;->onActivityLaunchFinished([BJ)V
+HPLcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;->onIntentStarted(Landroid/content/Intent;J)V
+HPLcom/google/android/startop/iorap/IorapForwardingService;->invokeRemote(Lcom/google/android/startop/iorap/IIorap;Lcom/google/android/startop/iorap/IorapForwardingService$RemoteRunnable;)Z
+HPLcom/android/server/am/HostingRecord;-><init>(Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;IZ)V
+HPLcom/android/server/am/ProcessList;->startProcessLocked(Ljava/lang/String;Landroid/content/pm/ApplicationInfo;ZILcom/android/server/am/HostingRecord;ZZIZLjava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/Runnable;)Lcom/android/server/am/ProcessRecord;
+HPLcom/android/server/am/ProcessRecord;->updateProcessInfo(ZZZ)V
+HPLcom/android/server/appprediction/RemoteAppPredictionService;->lambda$notifyAppTargetEvent$1(Landroid/app/prediction/AppPredictionSessionId;Landroid/app/prediction/AppTargetEvent;Landroid/service/appprediction/IPredictionService;)V
+HPLcom/android/server/audio/SoundEffectsHelper$SfxHandler$1;->run(Z)V
+HPLcom/android/server/input/InputManagerService$InputDevicesChangedListenerRecord;-><init>(Lcom/android/server/input/InputManagerService;ILandroid/hardware/input/IInputDevicesChangedListener;)V
+HPLcom/android/server/pm/PackageManagerServiceUtils;->enforceShellRestriction(Landroid/os/UserManagerInternal;Ljava/lang/String;II)V
+HPLcom/android/server/statusbar/StatusBarManagerService$1;->setSystemUiVisibility(IIIIILandroid/graphics/Rect;Landroid/graphics/Rect;ZLjava/lang/String;)V
+HPLcom/android/server/wm/-$$Lambda$AppTransitionController$o_nkoN7a-ZHaSAgJCQZcboKz9Ig;->test(Ljava/lang/Object;)Z
+HPLcom/android/server/wm/-$$Lambda$AppTransitionController$z5kCoexPNTWFncmRBfeXr6HA2JA;->test(Ljava/lang/Object;)Z
+HPLcom/android/server/wm/-$$Lambda$TaskPersister$8TcnoL7JFvpj8NzBRg91ns5JOBw;-><init>(Lcom/android/server/wm/TaskRecord;)V
+HPLcom/android/server/wm/ActivityRecord;->getAnimationBounds(I)Landroid/graphics/Rect;
+HPLcom/android/server/wm/ActivityRecord;->logStartActivity(ILcom/android/server/wm/TaskRecord;)V
+HPLcom/android/server/wm/ActivityRecord;->shouldAnimate(I)Z
+HPLcom/android/server/wm/ActivityStack;->getRunningTasks(Ljava/util/List;IIIZZLandroid/util/ArraySet;)V
+HPLcom/android/server/wm/ActivityStarter;->computeTargetTask(Lcom/android/server/wm/ActivityRecord;)Lcom/android/server/wm/TaskRecord;
+HPLcom/android/server/wm/ActivityStarter;->deliverToCurrentTopIfNeeded(Lcom/android/server/wm/ActivityStack;)I
+HPLcom/android/server/wm/ActivityStarter;->isAllowedToStart(Lcom/android/server/wm/ActivityRecord;ZLcom/android/server/wm/TaskRecord;)I
+HPLcom/android/server/wm/ActivityStarter;->setInitialState(Lcom/android/server/wm/ActivityRecord;Landroid/app/ActivityOptions;Lcom/android/server/wm/TaskRecord;ZILcom/android/server/wm/ActivityRecord;Landroid/service/voice/IVoiceInteractionSession;Lcom/android/internal/app/IVoiceInteractor;Z)V
+HPLcom/android/server/wm/ActivityStarter;->setNewTask(Lcom/android/server/wm/TaskRecord;)V
+HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/DisplayRotation;->updateRotationUnchecked(Z)Z
+HPLcom/android/server/wm/RemoteAnimationController;->createRemoteAnimationRecord(Lcom/android/server/wm/ActivityRecord;Landroid/graphics/Point;Landroid/graphics/Rect;Landroid/graphics/Rect;)Lcom/android/server/wm/RemoteAnimationController$RemoteAnimationRecord;
+HPLcom/android/server/wm/RemoteAnimationController;->linkToDeathOfRunner()V
+HPLcom/android/server/wm/RemoteAnimationController;->writeStartDebugStatement()V
+HPLcom/android/server/wm/TaskRecord;->setMinDimensions(Landroid/content/pm/ActivityInfo;)V
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->startWallpaperAnimations(Lcom/android/server/wm/WindowManagerService;JJLjava/util/function/Consumer;Ljava/util/ArrayList;)[Landroid/view/RemoteAnimationTarget;
+HPLcom/android/server/wm/WindowStateAnimator;->finishDrawingLocked(Landroid/view/SurfaceControl$Transaction;)Z
+HPLcom/google/android/startop/iorap/-$$Lambda$IorapForwardingService$AppLaunchObserver$6ikxM-3KospNGDidAY7yA-rECHw;->run(Lcom/google/android/startop/iorap/IIorap;)V
+HPLcom/google/android/startop/iorap/-$$Lambda$IorapForwardingService$AppLaunchObserver$B9wq4q5y7qahY6TuLMO_s8nPIwY;->run(Lcom/google/android/startop/iorap/IIorap;)V
+HPLcom/google/android/startop/iorap/-$$Lambda$IorapForwardingService$AppLaunchObserver$J1AHa-Qs75WQr3stjbN97THbudE;->run(Lcom/google/android/startop/iorap/IIorap;)V
+HPLcom/android/server/am/ProcessList;->newProcessRecordLocked(Landroid/content/pm/ApplicationInfo;Ljava/lang/String;ZILcom/android/server/am/HostingRecord;)Lcom/android/server/am/ProcessRecord;
+HPLcom/android/server/appprediction/RemoteAppPredictionService;->lambda$notifyAppTargetEvent$1(Landroid/app/prediction/AppPredictionSessionId;Landroid/app/prediction/AppTargetEvent;Landroid/service/appprediction/IPredictionService;)V
+HPLcom/android/server/audio/SoundEffectsHelper;->onPlaySoundEffect(II)V
+HPLcom/android/server/LocationManagerService;->isThrottlingExemptLocked(Lcom/android/server/location/CallerIdentity;)Z
+HPLcom/android/server/statusbar/StatusBarManagerService;->setSystemUiVisibility(IIIIILandroid/graphics/Rect;Landroid/graphics/Rect;ZLjava/lang/String;)V
+HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;->dump(Ljava/io/PrintWriter;Ljava/lang/String;)V
+HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationRecord;-><init>(Lcom/android/server/wm/RemoteAnimationController;Lcom/android/server/wm/ActivityRecord;Landroid/graphics/Point;Landroid/graphics/Rect;Landroid/graphics/Rect;)V
+HPLcom/android/server/wm/WindowProcessController;->setBoundClientUids(Landroid/util/ArraySet;)V
+HPLcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;->lambda$onActivityLaunched$2$IorapForwardingService$AppLaunchObserver([BILcom/google/android/startop/iorap/IIorap;)V
+HPLcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;->lambda$onActivityLaunchFinished$4$IorapForwardingService$AppLaunchObserver([BJLcom/google/android/startop/iorap/IIorap;)V
+HPLcom/google/android/startop/iorap/IorapForwardingService$AppLaunchObserver;->lambda$onIntentStarted$0$IorapForwardingService$AppLaunchObserver(Landroid/content/Intent;JLcom/google/android/startop/iorap/IIorap;)V
+HPLcom/android/server/statusbar/StatusBarManagerService;->updateUiVisibilityLocked(IIIIILandroid/graphics/Rect;Landroid/graphics/Rect;Z)V
+HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/google/android/startop/iorap/IIorap$Stub$Proxy;->onAppLaunchEvent(Lcom/google/android/startop/iorap/RequestId;Lcom/google/android/startop/iorap/AppLaunchEvent;)V
+HPLcom/google/android/startop/iorap/RequestId;->nextValueForSequence()Lcom/google/android/startop/iorap/RequestId;
+HPLcom/android/server/AlarmManagerService;->decrementAlarmCount(II)V
+HPLcom/android/server/am/ActivityManagerService$4;->newArray(I)[Landroid/content/IntentFilter;
+HPLcom/android/server/am/ActivityManagerService$PidMap;->remove(Lcom/android/server/am/ProcessRecord;)V
+HPLcom/android/server/am/ActivityManagerService;->updateOomAdjLocked(Ljava/lang/String;)V
+HPLcom/android/server/am/ActivityManagerShellCommand$1;-><init>(Lcom/android/server/am/ActivityManagerShellCommand;)V
+HPLcom/android/server/am/ActivityManagerShellCommand;-><init>(Lcom/android/server/am/ActivityManagerService;Z)V
+HPLcom/android/server/am/ActivityManagerShellCommand;->runStartActivity(Ljava/io/PrintWriter;)I
+HPLcom/android/server/am/HostingRecord;-><init>(Ljava/lang/String;Landroid/content/ComponentName;I)V
+HPLcom/android/server/am/PendingIntentController;->registerIntentSenderCancelListener(Landroid/content/IIntentSender;Lcom/android/internal/os/IResultReceiver;)V
+HPLcom/android/server/appprediction/-$$Lambda$AppPredictionManagerService$PredictionManagerServiceStub$vSY20eQq5y5FXrxhhqOTcEmezTs;-><init>(Landroid/app/prediction/AppPredictionSessionId;)V
+HPLcom/android/server/appprediction/-$$Lambda$RemoteAppPredictionService$9DCowUTEF8fYuBlWGxOmP5hTAWA;-><init>(Landroid/app/prediction/AppPredictionSessionId;)V
+HPLcom/android/server/appprediction/RemoteAppPredictionService;->lambda$requestPredictionUpdate$6(Landroid/app/prediction/AppPredictionSessionId;Landroid/service/appprediction/IPredictionService;)V
+HPLcom/android/server/autofill/-$$Lambda$AutofillManagerService$1$1-WNu3tTkxodB_LsZ7dGIlvrPN0;->visit(Ljava/lang/Object;)V
+HPLcom/android/server/autofill/ui/-$$Lambda$AutoFillUI$56AC3ykfo4h_e2LSjdkJ3XQn370;-><init>(Lcom/android/server/autofill/ui/AutoFillUI;Lcom/android/server/autofill/ui/AutoFillUI$AutoFillUiCallback;)V
+HPLcom/android/server/contentsuggestions/-$$Lambda$RemoteContentSuggestionsService$Enqw46SYVKFK9F2xX4qUcIu5_3I;->run(Landroid/os/IInterface;)V
+HPLcom/android/server/contentsuggestions/-$$Lambda$RemoteContentSuggestionsService$eoGnQ2MDLLnW1UBX6wxNE1VBLAk;->run(Landroid/os/IInterface;)V
+HPLcom/android/server/contentsuggestions/-$$Lambda$RemoteContentSuggestionsService$VKh1DoMPNSPjPfnVGdsInmxuqzc;->run(Landroid/os/IInterface;)V
+HPLcom/android/server/contentsuggestions/-$$Lambda$RemoteContentSuggestionsService$yUTbcaYlZCYTmagCkNJ3i2VCkY4;->run(Landroid/os/IInterface;)V
+HPLcom/android/server/contentsuggestions/RemoteContentSuggestionsService;->getServiceInterface(Landroid/os/IBinder;)Landroid/os/IInterface;
+HPLcom/android/server/display/AutomaticBrightnessController;->updateAutoBrightness(ZZ)V
+HPLcom/android/server/display/DisplayPowerController$BrightnessReason;->setModifier(I)V
+HPLcom/android/server/display/DisplayPowerController$BrightnessReason;->setReason(I)V
+HPLcom/android/server/display/DisplayPowerController$BrightnessReason;->toString(I)Ljava/lang/String;
+HPLcom/android/server/display/DisplayPowerController$BrightnessReason;->toString()Ljava/lang/String;
+HPLcom/android/server/media/projection/MediaProjectionManagerService$1;->onProcessDied(II)V
+HPLcom/android/server/net/NetworkPolicyManagerService;->removeUidStateUL(I)Z
+HPLcom/android/server/pm/permission/PermissionManagerService;->addOnPermissionsChangeListener(Landroid/permission/IOnPermissionsChangeListener;)V
+HPLcom/android/server/protolog/ProtoLogImpl;->getSingleInstance()Lcom/android/server/protolog/ProtoLogImpl;
+HPLcom/android/server/soundtrigger/SoundTriggerHelper$PowerSaveModeListener;-><init>(Lcom/android/server/soundtrigger/SoundTriggerHelper;)V
+HPLcom/android/server/statusbar/-$$Lambda$StatusBarManagerService$uF0ibEnnXe7Lxunxb98QQLJjgZM;->run()V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->setSystemUiState(IIILandroid/graphics/Rect;Landroid/graphics/Rect;Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->systemUiStateEquals(IIILandroid/graphics/Rect;Landroid/graphics/Rect;Z)Z
+HPLcom/android/server/usage/UsageStatsDatabase;->filterStats(Lcom/android/server/usage/IntervalStats;)V
+HPLcom/android/server/VibratorService$VibrationInfo;-><init>(JLandroid/os/VibrationEffect;Landroid/os/VibrationEffect;Landroid/media/AudioAttributes;ILjava/lang/String;Ljava/lang/String;)V
+HPLcom/android/server/VibratorService;->getAppOpMode(Lcom/android/server/VibratorService$Vibration;)I
+HPLcom/android/server/VibratorService;->getCurrentIntensityLocked(Lcom/android/server/VibratorService$Vibration;)I
+HPLcom/android/server/VibratorService;->isAllowedToVibrateLocked(Lcom/android/server/VibratorService$Vibration;)Z
+HPLcom/android/server/VibratorService;->vibrate(ILjava/lang/String;Landroid/os/VibrationEffect;Landroid/media/AudioAttributes;Ljava/lang/String;Landroid/os/IBinder;)V
+HPLcom/android/server/wm/-$$Lambda$ActivityRecord$g49I60MBbnNkxHlgA-NR7ALwWTQ;->apply(Ljava/lang/Object;)Z
+HPLcom/android/server/wm/-$$Lambda$ActivityRecord$pBc6yUdDV5IrUd9vt6oCz6QzpiE;->accept(Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$BEx3OWenCvYAaV5h_J2ZkZXhEcY;->accept(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$Zbxkj4wIhcDki6VwBh1kWmSmxqM;-><init>(Lcom/android/server/wm/DisplayPolicy;IIILandroid/graphics/Rect;Landroid/graphics/Rect;ZLcom/android/server/wm/WindowState;I[Lcom/android/internal/view/AppearanceRegion;ZZ)V
+HPLcom/android/server/wm/-$$Lambda$DisplayPolicy$Zbxkj4wIhcDki6VwBh1kWmSmxqM;->run()V
+HPLcom/android/server/wm/-$$Lambda$TaskChangeNotificationController$UexNbaqPy0mc3VxTw2coCctHho8;->accept(Landroid/app/ITaskStackListener;Landroid/os/Message;)V
+HPLcom/android/server/wm/ActivityMetricsLogger;->allWindowsDrawn()Z
+HPLcom/android/server/wm/ActivityRecord;->createAnimationBoundsLayer(Landroid/view/SurfaceControl$Transaction;)Landroid/view/SurfaceControl;
+HPLcom/android/server/wm/ActivityRecord;->destroyed(Ljava/lang/String;)V
+HPLcom/android/server/wm/ActivityRecord;->forAllWindowsUnchecked(Lcom/android/internal/util/ToBooleanFunction;Z)Z
+HPLcom/android/server/wm/ActivityRecord;->getTopFullscreenWindow()Lcom/android/server/wm/WindowState;
+HPLcom/android/server/wm/ActivityRecord;->loadAnimation(Landroid/view/WindowManager$LayoutParams;IZZ)Landroid/view/animation/Animation;
+HPLcom/android/server/wm/ActivityRecord;->notifyAppStopped()V
+HPLcom/android/server/wm/ActivityRecord;->onFirstWindowDrawn(Lcom/android/server/wm/WindowState;Lcom/android/server/wm/WindowStateAnimator;)V
+HPLcom/android/server/wm/ActivityRecord;->onRemovedFromDisplay()V
+HPLcom/android/server/wm/ActivityRecord;->removeDeadWindows()V
+HPLcom/android/server/wm/ActivityRecord;->setClientHidden(Z)V
+HPLcom/android/server/wm/ActivityRecord;->stopFreezingScreenLocked(Z)V
+HPLcom/android/server/wm/ActivityStack;->removeTimeoutsForActivity(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/ActivityStarter;->recycleTask(Lcom/android/server/wm/TaskRecord;Lcom/android/server/wm/ActivityRecord;Lcom/android/server/wm/ActivityRecord;[Lcom/android/server/wm/ActivityRecord;)I
+HPLcom/android/server/wm/ActivityTaskManagerService;->getTaskSnapshot(IZZ)Landroid/app/ActivityManager$TaskSnapshot;
+HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/DisplayContent;->getWindowCornerRadius()F
+HPLcom/android/server/wm/DisplayPolicy;->convertNonDecorInsetsToStableInsets(Landroid/graphics/Rect;I)V
+HPLcom/android/server/wm/DisplayPolicy;->getInsetsPolicy()Lcom/android/server/wm/InsetsPolicy;
+HPLcom/android/server/wm/DisplayPolicy;->updateLightStatusBarAppearanceLw(ILcom/android/server/wm/WindowState;Lcom/android/server/wm/WindowState;)I
+HPLcom/android/server/wm/InsetsPolicy;->getInsetsForDispatch(Lcom/android/server/wm/WindowState;)Landroid/view/InsetsState;
+HPLcom/android/server/wm/InsetsPolicy;->isHidden(I)Z
+HPLcom/android/server/wm/InsetsPolicy;->updateBarControlTarget(Lcom/android/server/wm/WindowState;)V
+HPLcom/android/server/wm/Task;->getTopFullscreenActivity()Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/TaskRecord;->removeTaskActivitiesLocked(Ljava/lang/String;)V
+HPLcom/android/server/wm/Task;->removeChild(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/TaskSnapshotCache$CacheEntry;-><init>(Landroid/app/ActivityManager$TaskSnapshot;Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/TaskSnapshotController;->createTaskSnapshot(Lcom/android/server/wm/Task;F)Landroid/view/SurfaceControl$ScreenshotGraphicBuffer;
+HPLcom/android/server/wm/TaskSnapshotController;->findAppTokenForSnapshot(Lcom/android/server/wm/Task;)Lcom/android/server/wm/ActivityRecord;
+HPLcom/android/server/wm/TaskSnapshotPersister$DeleteWriteQueueItem;-><init>(Lcom/android/server/wm/TaskSnapshotPersister;II)V
+HPLcom/android/server/wm/TaskSnapshotPersister$StoreWriteQueueItem;-><init>(Lcom/android/server/wm/TaskSnapshotPersister;IILandroid/app/ActivityManager$TaskSnapshot;)V
+HPLcom/android/server/wm/TaskSnapshotPersister$StoreWriteQueueItem;->onQueuedLocked()V
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->lambda$startWallpaperAnimations$0(JJLjava/util/function/Consumer;Ljava/util/ArrayList;Ljava/util/ArrayList;Lcom/android/server/wm/WallpaperWindowToken;)V
+HPLcom/android/server/wm/WallpaperAnimationAdapter;->startWallpaperAnimations(Lcom/android/server/wm/WindowManagerService;JJLjava/util/function/Consumer;Ljava/util/ArrayList;)[Landroid/view/RemoteAnimationTarget;
+HPLcom/android/server/wm/WindowAnimationSpec;->findTranslateAnimation(Landroid/view/animation/Animation;)Landroid/view/animation/TranslateAnimation;
+HPLcom/android/server/wm/WindowProcessControllerMap;->removeProcessFromUidMap(Lcom/android/server/wm/WindowProcessController;)V
+HPLcom/android/server/wm/WindowProcessController;->updateProcessInfo(ZZZ)V
+HPLcom/android/server/wm/WindowState$DeathRecipient;-><init>(Lcom/android/server/wm/WindowState;Lcom/android/server/wm/WindowState$1;)V
+HPLcom/android/server/wm/WindowState$WindowId;-><init>(Lcom/android/server/wm/WindowState;Lcom/android/server/wm/WindowState$1;)V
+HPLcom/google/android/startop/iorap/AppLaunchEvent$ActivityLaunched;->writeToParcelImpl(Landroid/os/Parcel;I)V
+HPLcom/google/android/startop/iorap/AppLaunchEvent$ActivityLaunchFinished;->writeToParcelImpl(Landroid/os/Parcel;I)V
+HPLcom/google/android/startop/iorap/AppLaunchEvent$IntentStarted;->writeToParcelImpl(Landroid/os/Parcel;I)V
+HPLcom/google/android/startop/iorap/AppLaunchEvent;->getTypeIndex()I
+HPLcom/android/server/pm/PackageManagerService;->access$7600(Lcom/android/server/pm/PackageManagerService;Landroid/content/Intent;Ljava/lang/String;IIZI)Landroid/content/pm/ResolveInfo;
+HPLcom/android/server/pm/PackageManagerService;->access$8100(Lcom/android/server/pm/PackageManagerService;II)Z
+HPLcom/android/server/wm/ActivityRecord$Token;->access$100(Lcom/android/server/wm/ActivityRecord$Token;Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;->access$000(Lcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;)Lcom/android/server/wm/SurfaceAnimator$OnAnimationFinishedCallback;
+HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;->access$400(Lcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;)Landroid/graphics/Point;
+HPLcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;->access$500(Lcom/android/server/wm/RemoteAnimationController$RemoteAnimationAdapterWrapper;)Landroid/graphics/Rect;
+HPLcom/android/server/wm/RemoteAnimationController;->access$100(Lcom/android/server/wm/RemoteAnimationController;)V
+HPLcom/android/server/wm/RemoteAnimationController;->access$300(Lcom/android/server/wm/RemoteAnimationController;)Landroid/view/RemoteAnimationAdapter;
+HPLcom/android/server/wm/TaskPersister$TaskWriteQueueItem;->access$200(Lcom/android/server/wm/TaskPersister$TaskWriteQueueItem;)Lcom/android/server/wm/TaskRecord;
+HPLcom/android/server/wm/TaskPersister;->access$000(I)Ljava/io/File;
+HPLcom/android/server/wm/WindowContainer;->access$100(Lcom/android/server/wm/WindowContainer;)Landroid/util/Pools$SynchronizedPool;
+HPLcom/android/server/wm/WindowManagerService;->access$1600(Lcom/android/server/wm/WindowManagerService;Landroid/os/IBinder;)V
+HPLcom/android/server/pm/ShortcutUser;->logSharingShortcutStats(Lcom/android/internal/logging/MetricsLogger;)V
+HPLcom/android/server/statusbar/StatusBarManagerService;->lambda$topAppWindowChanged$1$StatusBarManagerService(IZZ)V
+HPLcom/android/server/wm/ActivityRecord$Token;->attach(Lcom/android/server/wm/ActivityRecord;)V
+HPLcom/android/server/wm/AnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/DisplayPolicy;->lambda$updateSystemUiVisibilityLw$10$DisplayPolicy(IIILandroid/graphics/Rect;Landroid/graphics/Rect;ZLcom/android/server/wm/WindowState;I[Lcom/android/internal/view/AppearanceRegion;ZZ)V
+HPLcom/android/server/wm/InsetsSourceProvider;->hasWindow()Z
+HPLcom/android/server/wm/InsetsStateController;->onBarControlTargetChanged(Lcom/android/server/wm/InsetsControlTarget;Lcom/android/server/wm/InsetsControlTarget;Lcom/android/server/wm/InsetsControlTarget;Lcom/android/server/wm/InsetsControlTarget;)V
+HPLcom/android/server/wm/InsetsStateController;->peekSourceProvider(I)Lcom/android/server/wm/InsetsSourceProvider;
+HPLcom/android/server/wm/TaskPersister$TaskWriteQueueItem;->access$200(Lcom/android/server/wm/TaskPersister$TaskWriteQueueItem;)Lcom/android/server/wm/TaskRecord;
+HPLcom/android/server/wm/WindowContainer;->access$100(Lcom/android/server/wm/WindowContainer;)Landroid/util/Pools$SynchronizedPool;
+HPLcom/android/server/-$$Lambda$AlarmManagerService$2$Eo-D98J-N9R2METkD-12gPs320c;-><init>(Lcom/android/server/AlarmManagerService$2;Landroid/app/IAlarmCompleteListener;)V
+HPLcom/android/server/AlarmManagerService;->access$100(Lcom/android/server/AlarmManagerService;)Lcom/android/server/AlarmManagerService$Injector;
+HPLcom/android/server/AlarmManagerService;->access$2102(Lcom/android/server/AlarmManagerService;J)J
+HPLcom/android/server/AlarmManagerService;->access$2202(Lcom/android/server/AlarmManagerService;J)J
+HPLcom/android/server/AlarmManagerService;->access$2300(Lcom/android/server/AlarmManagerService;)V
+HPLcom/android/server/AlarmManagerService;->access$2400(Lcom/android/server/AlarmManagerService;Lcom/android/server/AlarmManagerService$Alarm;)Z
+HPLcom/android/server/AlarmManagerService;->isExemptFromAppStandby(Lcom/android/server/AlarmManagerService$Alarm;)Z
+HPLcom/android/server/appwidget/AppWidgetServiceImpl$HostId;-><init>(IILjava/lang/String;)V
+HPLcom/android/server/content/ContentService$ObserverCall;->run()V
+HPLcom/android/server/statusbar/-$$Lambda$StatusBarManagerService$uF0ibEnnXe7Lxunxb98QQLJjgZM;-><init>(Lcom/android/server/statusbar/StatusBarManagerService;IZZ)V
+HPLcom/android/server/statusbar/StatusBarManagerService$1;->topAppWindowChanged(IZZ)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->access$1600(Lcom/android/server/statusbar/StatusBarManagerService$UiState;Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->access$1700(Lcom/android/server/statusbar/StatusBarManagerService$UiState;Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->setFullscreen(Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService$UiState;->setImmersive(Z)V
+HPLcom/android/server/statusbar/StatusBarManagerService;->access$600(Lcom/android/server/statusbar/StatusBarManagerService;IZZ)V
+HPLcom/android/server/statusbar/StatusBarManagerService;->enforceStatusBar()V
+HPLcom/android/server/statusbar/StatusBarManagerService;->getUiState(I)Lcom/android/server/statusbar/StatusBarManagerService$UiState;
+HPLcom/android/server/statusbar/StatusBarManagerService;->topAppWindowChanged(IZZ)V
+HPLcom/android/server/wm/InsetsStateController;->onControlFakeTargetChanged(ILcom/android/server/wm/InsetsControlTarget;)V
+HPLcom/android/server/wm/LocalAnimationAdapter$AnimationSpec;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
+HPLcom/android/server/wm/LocalAnimationAdapter;->writeToProto(Landroid/util/proto/ProtoOutputStream;)V
+HPLcom/android/server/wm/PersisterQueue;->access$100(Lcom/android/server/wm/PersisterQueue;)Ljava/util/ArrayList;
+HPLcom/android/server/wm/PersisterQueue;->access$200(Lcom/android/server/wm/PersisterQueue;)Ljava/util/ArrayList;
+HPLcom/android/server/wm/PersisterQueue;->access$300(Lcom/android/server/wm/PersisterQueue;)V
+HPLcom/android/server/wm/TaskSnapshotPersister;->access$100(Lcom/android/server/wm/TaskSnapshotPersister;)Ljava/lang/Object;
+HPLcom/android/server/wm/TaskSnapshotPersister;->access$200(Lcom/android/server/wm/TaskSnapshotPersister;)Z
+HPLcom/android/server/wm/TaskSnapshotPersister;->access$300(Lcom/android/server/wm/TaskSnapshotPersister;)Ljava/util/ArrayDeque;
+HPLcom/android/server/wm/TaskSnapshotPersister;->access$402(Lcom/android/server/wm/TaskSnapshotPersister;Z)Z
+HPLcom/android/server/wm/WindowAnimationSpec;->findTranslateAnimation(Landroid/view/animation/Animation;)Landroid/view/animation/TranslateAnimation;
+HPLcom/android/server/wm/WindowAnimationSpec;->writeToProtoInner(Landroid/util/proto/ProtoOutputStream;)V
+HPLcom/android/server/wm/WindowContainer;->access$100(Lcom/android/server/wm/WindowContainer;)Landroid/util/Pools$SynchronizedPool;
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 7d129ea..1fc4751 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -197,8 +197,20 @@
         if ((type & SaveInfo.SAVE_DATA_TYPE_ADDRESS) != 0) {
             types.add(context.getString(R.string.autofill_save_type_address));
         }
-        if ((type & SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD) != 0) {
+
+        // fallback to generic card type if set multiple types
+        final int cardTypeMask = SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD
+                        | SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD
+                        | SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
+        final int count = Integer.bitCount(type & cardTypeMask);
+        if (count > 1 || (type & SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD) != 0) {
+            types.add(context.getString(R.string.autofill_save_type_generic_card));
+        } else if ((type & SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD) != 0) {
+            types.add(context.getString(R.string.autofill_save_type_payment_card));
+        } else if ((type & SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD) != 0) {
             types.add(context.getString(R.string.autofill_save_type_credit_card));
+        } else if ((type & SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD) != 0) {
+            types.add(context.getString(R.string.autofill_save_type_debit_card));
         }
         if ((type & SaveInfo.SAVE_DATA_TYPE_USERNAME) != 0) {
             types.add(context.getString(R.string.autofill_save_type_username));
@@ -269,7 +281,9 @@
         noButton.setOnClickListener((v) -> mListener.onCancel(info.getNegativeActionListener()));
 
         final TextView yesButton = view.findViewById(R.id.autofill_save_yes);
-        if (isUpdate) {
+        if (info.getPositiveActionStyle() == SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE) {
+            yesButton.setText(R.string.autofill_continue_yes);
+        } else if (isUpdate) {
             yesButton.setText(R.string.autofill_update_yes);
         }
         yesButton.setOnClickListener((v) -> mListener.onSave());
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 4f4e47a..3067beb 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -21,6 +21,7 @@
     cmd: "$(location protologtool) transform-protolog-calls " +
       "--protolog-class com.android.server.protolog.common.ProtoLog " +
       "--protolog-impl-class com.android.server.protolog.ProtoLogImpl " +
+      "--protolog-cache-class 'com.android.server.protolog.ProtoLog$$Cache' " +
       "--loggroups-class com.android.server.wm.ProtoLogGroup " +
       "--loggroups-jar $(location :services.core.wm.protologgroups) " +
       "--output-srcjar $(out) " +
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index d18b4f6..a33fcd5 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -720,6 +720,8 @@
         event.putInt(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
         event.putInt(BatteryManager.EXTRA_PLUGGED, mPlugType);
         event.putInt(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltage);
+        event.putInt(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperature);
+        event.putInt(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
         event.putLong(BatteryManager.EXTRA_EVENT_TIMESTAMP, now);
 
         boolean queueWasEmpty = mBatteryLevelsEventQueue.isEmpty();
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 0bb72cb..ce0e9e7 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -26,6 +26,7 @@
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
@@ -528,6 +529,15 @@
     private static final int EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY = 45;
 
     /**
+     * Event for NetworkMonitor to inform ConnectivityService that the probe status has changed.
+     * Both of the arguments are bitmasks, and the value of bits come from
+     * INetworkMonitor.NETWORK_VALIDATION_PROBE_*.
+     * arg1 = A bitmask to describe which probes are completed.
+     * arg2 = A bitmask to describe which probes are successful.
+     */
+    public static final int EVENT_PROBE_STATUS_CHANGED = 46;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -2663,6 +2673,41 @@
             switch (msg.what) {
                 default:
                     return false;
+                case EVENT_PROBE_STATUS_CHANGED: {
+                    final Integer netId = (Integer) msg.obj;
+                    final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
+                    if (nai == null) {
+                        break;
+                    }
+                    final boolean probePrivateDnsCompleted =
+                            ((msg.arg1 & NETWORK_VALIDATION_PROBE_PRIVDNS) != 0);
+                    final boolean privateDnsBroken =
+                            ((msg.arg2 & NETWORK_VALIDATION_PROBE_PRIVDNS) == 0);
+                    if (probePrivateDnsCompleted) {
+                        if (nai.networkCapabilities.isPrivateDnsBroken() != privateDnsBroken) {
+                            nai.networkCapabilities.setPrivateDnsBroken(privateDnsBroken);
+                            final int oldScore = nai.getCurrentScore();
+                            updateCapabilities(oldScore, nai, nai.networkCapabilities);
+                        }
+                        // Only show the notification when the private DNS is broken and the
+                        // PRIVATE_DNS_BROKEN notification hasn't shown since last valid.
+                        if (privateDnsBroken && !nai.networkMisc.hasShownBroken) {
+                            showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN);
+                        }
+                        nai.networkMisc.hasShownBroken = privateDnsBroken;
+                    } else if (nai.networkCapabilities.isPrivateDnsBroken()) {
+                        // If probePrivateDnsCompleted is false but nai.networkCapabilities says
+                        // private DNS is broken, it means this network is being reevaluated.
+                        // Either probing private DNS is not necessary any more or it hasn't been
+                        // done yet. In either case, the networkCapabilities should be updated to
+                        // reflect the new status.
+                        nai.networkCapabilities.setPrivateDnsBroken(false);
+                        final int oldScore = nai.getCurrentScore();
+                        updateCapabilities(oldScore, nai, nai.networkCapabilities);
+                        nai.networkMisc.hasShownBroken = false;
+                    }
+                    break;
+                }
                 case EVENT_NETWORK_TESTED: {
                     final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
                     if (nai == null) break;
@@ -2705,14 +2750,20 @@
                         if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                         if (valid) {
                             handleFreshlyValidatedNetwork(nai);
-                            // Clear NO_INTERNET, PARTIAL_CONNECTIVITY and LOST_INTERNET
-                            // notifications if network becomes valid.
+                            // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and
+                            // LOST_INTERNET notifications if network becomes valid.
                             mNotifier.clearNotification(nai.network.netId,
                                     NotificationType.NO_INTERNET);
                             mNotifier.clearNotification(nai.network.netId,
                                     NotificationType.LOST_INTERNET);
                             mNotifier.clearNotification(nai.network.netId,
                                     NotificationType.PARTIAL_CONNECTIVITY);
+                            mNotifier.clearNotification(nai.network.netId,
+                                    NotificationType.PRIVATE_DNS_BROKEN);
+                            // If network becomes valid, the hasShownBroken should be reset for
+                            // that network so that the notification will be fired when the private
+                            // DNS is broken again.
+                            nai.networkMisc.hasShownBroken = false;
                         }
                     } else if (partialConnectivityChanged) {
                         updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -2863,6 +2914,13 @@
         }
 
         @Override
+        public void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) {
+            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+                    EVENT_PROBE_STATUS_CHANGED,
+                    probesCompleted, probesSucceeded, new Integer(mNetId)));
+        }
+
+        @Override
         public void showProvisioningNotification(String action, String packageName) {
             final Intent intent = new Intent(action);
             intent.setPackage(packageName);
@@ -3679,6 +3737,11 @@
                 // High priority because it is only displayed for explicitly selected networks.
                 highPriority = true;
                 break;
+            case PRIVATE_DNS_BROKEN:
+                action = Settings.ACTION_WIRELESS_SETTINGS;
+                // High priority because we should let user know why there is no internet.
+                highPriority = true;
+                break;
             case LOST_INTERNET:
                 action = ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION;
                 // High priority because it could help the user avoid unexpected data usage.
@@ -3696,7 +3759,7 @@
         }
 
         Intent intent = new Intent(action);
-        if (type != NotificationType.LOGGED_IN) {
+        if (type != NotificationType.LOGGED_IN && type != NotificationType.PRIVATE_DNS_BROKEN) {
             intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             intent.setClassName("com.android.settings",
@@ -5162,6 +5225,13 @@
         ns.assertValidFromUid(Binder.getCallingUid());
     }
 
+    private void ensureValid(NetworkCapabilities nc) {
+        ensureValidNetworkSpecifier(nc);
+        if (nc.isPrivateDnsBroken()) {
+            throw new IllegalArgumentException("Can't request broken private DNS");
+        }
+    }
+
     @Override
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
@@ -5195,7 +5265,7 @@
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Bad timeout specified");
         }
-        ensureValidNetworkSpecifier(networkCapabilities);
+        ensureValid(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), type);
@@ -5337,7 +5407,7 @@
         // There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
         // can't request networks.
         restrictBackgroundRequestForCaller(nc);
-        ensureValidNetworkSpecifier(nc);
+        ensureValid(nc);
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
@@ -5355,7 +5425,7 @@
         if (!hasWifiNetworkListenPermission(networkCapabilities)) {
             enforceAccessPermission();
         }
-        ensureValidNetworkSpecifier(networkCapabilities);
+        ensureValid(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
                 Binder.getCallingPid(), Binder.getCallingUid());
 
@@ -5841,6 +5911,7 @@
         } else {
             newNc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
         }
+        newNc.setPrivateDnsBroken(nai.networkCapabilities.isPrivateDnsBroken());
 
         return newNc;
     }
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index 44a8234..274e2f1 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.Context;
@@ -300,7 +301,7 @@
      * @return true if callback is successfully added, false otherwise
      */
     public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
-            @NonNull String listenerIdentity) {
+            @Nullable String featureId, @NonNull String listenerIdentity) {
         mContext.enforceCallingPermission(
                 android.Manifest.permission.LOCATION_HARDWARE,
                 "Location Hardware permission not granted to access hardware batching");
@@ -319,7 +320,7 @@
 
         CallerIdentity callerIdentity =
                 new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
-                        listenerIdentity);
+                        featureId, listenerIdentity);
         synchronized (mGnssBatchingLock) {
             mGnssBatchingCallback = callback;
             mGnssBatchingDeathCallback =
@@ -497,6 +498,7 @@
     private <TListener extends IInterface> boolean addGnssDataListenerLocked(
             TListener listener,
             String packageName,
+            @Nullable String featureId,
             @NonNull String listenerIdentifier,
             RemoteListenerHelper<TListener> gnssDataProvider,
             ArrayMap<IBinder,
@@ -517,7 +519,7 @@
 
         CallerIdentity callerIdentity =
                 new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
-                        listenerIdentifier);
+                        featureId, listenerIdentifier);
         LinkedListener<TListener> linkedListener =
                 new LocationManagerServiceUtils.LinkedListener<>(
                         listener, listenerIdentifier, callerIdentity, binderDeathCallback);
@@ -605,11 +607,13 @@
      * @param packageName name of requesting package
      * @return true if listener is successfully registered, false otherwise
      */
-    public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
+    public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
+            @Nullable String featureId) {
         synchronized (mGnssStatusListeners) {
             return addGnssDataListenerLocked(
                     listener,
                     packageName,
+                    featureId,
                     "Gnss status",
                     mGnssStatusProvider,
                     mGnssStatusListeners,
@@ -636,12 +640,13 @@
      * @return true if listener is successfully added, false otherwise
      */
     public boolean addGnssMeasurementsListener(
-            IGnssMeasurementsListener listener, String packageName,
+            IGnssMeasurementsListener listener, String packageName, @Nullable String featureId,
             @NonNull String listenerIdentifier) {
         synchronized (mGnssMeasurementsListeners) {
             return addGnssDataListenerLocked(
                     listener,
                     packageName,
+                    featureId,
                     listenerIdentifier,
                     mGnssMeasurementsProvider,
                     mGnssMeasurementsListeners,
@@ -695,11 +700,12 @@
      */
     public boolean addGnssNavigationMessageListener(
             IGnssNavigationMessageListener listener, String packageName,
-            @NonNull String listenerIdentifier) {
+            @Nullable String featureId, @NonNull String listenerIdentifier) {
         synchronized (mGnssNavigationMessageListeners) {
             return addGnssDataListenerLocked(
                     listener,
                     packageName,
+                    featureId,
                     listenerIdentifier,
                     mGnssNavigationMessageProvider,
                     mGnssNavigationMessageListeners,
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 1d22b82..0a63bf8 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1000,6 +1000,12 @@
 
         @Override
         public void onReportLocation(Location location) {
+            // likelihood of a 0,0 bug is far greater than this being a valid location
+            if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) {
+                Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
+                return;
+            }
+
             synchronized (mLock) {
                 handleLocationChangedLocked(location, this);
             }
@@ -1227,9 +1233,9 @@
         PowerManager.WakeLock mWakeLock;
 
         private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
-                String packageName, WorkSource workSource, boolean hideFromAppOps,
-                @NonNull String listenerIdentifier) {
-            super(new CallerIdentity(uid, pid, packageName, listenerIdentifier),
+                String packageName, @Nullable String featureId, WorkSource workSource,
+                boolean hideFromAppOps, @NonNull String listenerIdentifier) {
+            super(new CallerIdentity(uid, pid, packageName, featureId, listenerIdentifier),
                     "LocationListener");
             mListener = listener;
             mPendingIntent = intent;
@@ -1354,7 +1360,8 @@
             if (!currentlyMonitoring) {
                 if (allowMonitoring) {
                     return mAppOps.startOpNoThrow(op, mCallerIdentity.mUid,
-                            mCallerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
+                            mCallerIdentity.mPackageName, false, mCallerIdentity.mFeatureId, null)
+                            == AppOpsManager.MODE_ALLOWED;
                 }
             } else {
                 if (!allowMonitoring
@@ -1539,11 +1546,11 @@
 
     @Override
     public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
-            String listenerIdentifier) {
+            String featureId, String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
-                callback, packageName, listenerIdentifier);
+                callback, packageName, featureId, listenerIdentifier);
     }
 
     @Override
@@ -1706,10 +1713,10 @@
     }
 
     private boolean reportLocationAccessNoThrow(int pid, int uid, String packageName,
-            int allowedResolutionLevel, @Nullable String message) {
+            @Nullable String featureId, int allowedResolutionLevel, @Nullable String message) {
         int op = resolutionLevelToOp(allowedResolutionLevel);
         if (op >= 0) {
-            if (mAppOps.noteOpNoThrow(op, uid, packageName, null, message)
+            if (mAppOps.noteOpNoThrow(op, uid, packageName, featureId, message)
                     != AppOpsManager.MODE_ALLOWED) {
                 return false;
             }
@@ -2143,12 +2150,12 @@
 
     @GuardedBy("mLock")
     private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
-            String packageName, WorkSource workSource, boolean hideFromAppOps,
-            @NonNull String listenerIdentifier) {
+            String packageName, @Nullable String featureId, WorkSource workSource,
+            boolean hideFromAppOps, @NonNull String listenerIdentifier) {
         IBinder binder = listener.asBinder();
         Receiver receiver = mReceivers.get(binder);
         if (receiver == null) {
-            receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
+            receiver = new Receiver(listener, null, pid, uid, packageName, featureId, workSource,
                     hideFromAppOps, listenerIdentifier);
             if (!receiver.linkToListenerDeathNotificationLocked(
                     receiver.getListener().asBinder())) {
@@ -2161,10 +2168,11 @@
 
     @GuardedBy("mLock")
     private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
-            WorkSource workSource, boolean hideFromAppOps, @NonNull String listenerIdentifier) {
+            @Nullable String featureId, WorkSource workSource, boolean hideFromAppOps,
+            @NonNull String listenerIdentifier) {
         Receiver receiver = mReceivers.get(intent);
         if (receiver == null) {
-            receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
+            receiver = new Receiver(null, intent, pid, uid, packageName, featureId, workSource,
                     hideFromAppOps, listenerIdentifier);
             mReceivers.put(intent, receiver);
         }
@@ -2227,7 +2235,8 @@
 
     @Override
     public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
-            PendingIntent intent, String packageName, String listenerIdentifier) {
+            PendingIntent intent, String packageName, String featureId,
+            String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         synchronized (mLock) {
@@ -2283,11 +2292,11 @@
 
                 Receiver receiver;
                 if (intent != null) {
-                    receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
-                            hideFromAppOps, listenerIdentifier);
+                    receiver = getReceiverLocked(intent, pid, uid, packageName, featureId,
+                            workSource, hideFromAppOps, listenerIdentifier);
                 } else {
-                    receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
-                            hideFromAppOps, listenerIdentifier);
+                    receiver = getReceiverLocked(listener, pid, uid, packageName, featureId,
+                            workSource, hideFromAppOps, listenerIdentifier);
                 }
                 requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
             } finally {
@@ -2356,9 +2365,10 @@
         synchronized (mLock) {
             Receiver receiver;
             if (intent != null) {
-                receiver = getReceiverLocked(intent, pid, uid, packageName, null, false, "");
+                receiver = getReceiverLocked(intent, pid, uid, packageName, null, null, false, "");
             } else {
-                receiver = getReceiverLocked(listener, pid, uid, packageName, null, false, "");
+                receiver = getReceiverLocked(listener, pid, uid, packageName, null, null, false,
+                        "");
             }
 
             long identity = Binder.clearCallingIdentity();
@@ -2402,7 +2412,7 @@
     }
 
     @Override
-    public Location getLastLocation(LocationRequest r, String packageName) {
+    public Location getLastLocation(LocationRequest r, String packageName, String featureId) {
         synchronized (mLock) {
             LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
             int allowedResolutionLevel = getCallerAllowedResolutionLevel();
@@ -2477,8 +2487,8 @@
                 }
                 // Don't report location access if there is no last location to deliver.
                 if (lastLocation != null) {
-                    if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel,
-                            null)) {
+                    if (!reportLocationAccessNoThrow(pid, uid, packageName, featureId,
+                            allowedResolutionLevel, null)) {
                         if (D) {
                             Log.d(TAG, "not returning last loc for no op app: " + packageName);
                         }
@@ -2495,9 +2505,9 @@
     @Override
     public boolean getCurrentLocation(LocationRequest locationRequest,
             ICancellationSignal remoteCancellationSignal, ILocationListener listener,
-            String packageName, String listenerIdentifier) {
+            String packageName, String featureId, String listenerIdentifier) {
         // side effect of validating locationRequest and packageName
-        Location lastLocation = getLastLocation(locationRequest, packageName);
+        Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
         if (lastLocation != null) {
             long locationAgeMs = TimeUnit.NANOSECONDS.toMillis(
                     SystemClock.elapsedRealtimeNanos() - lastLocation.getElapsedRealtimeNanos());
@@ -2532,7 +2542,8 @@
             }
         }
 
-        requestLocationUpdates(locationRequest, listener, null, packageName, listenerIdentifier);
+        requestLocationUpdates(locationRequest, listener, null, packageName, featureId,
+                listenerIdentifier);
         CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
                 remoteCancellationSignal);
         if (cancellationSignal != null) {
@@ -2582,7 +2593,7 @@
 
     @Override
     public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
-            String packageName, String listenerIdentifier) {
+            String packageName, String featureId, String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         if (request == null) request = DEFAULT_LOCATION_REQUEST;
@@ -2630,7 +2641,7 @@
             }
 
             mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
-                    uid, packageName, listenerIdentifier);
+                    uid, packageName, featureId, listenerIdentifier);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -2666,9 +2677,10 @@
     }
 
     @Override
-    public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
+    public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
+            String featureId) {
         return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback(
-                listener, packageName);
+                listener, packageName, featureId);
     }
 
     @Override
@@ -2678,12 +2690,12 @@
 
     @Override
     public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
-            String packageName, String listenerIdentifier) {
+            String packageName, String featureId, String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName,
-                        listenerIdentifier);
+                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId,
+                       listenerIdentifier);
     }
 
     @Override
@@ -2711,12 +2723,12 @@
 
     @Override
     public boolean addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
-            String packageName, String listenerIdentifier) {
+            String packageName, String featureId, String listenerIdentifier) {
         Preconditions.checkNotNull(listenerIdentifier);
 
         return mGnssManagerService == null ? false
                 : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
-                        listenerIdentifier);
+                        featureId, listenerIdentifier);
     }
 
     @Override
@@ -3022,6 +3034,7 @@
                             receiver.mCallerIdentity.mPid,
                             receiver.mCallerIdentity.mUid,
                             receiver.mCallerIdentity.mPackageName,
+                            receiver.mCallerIdentity.mFeatureId,
                             receiver.mAllowedResolutionLevel,
                             "Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
                         if (D) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index a6ac17d..0d493b8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1018,7 +1018,14 @@
 
         synchronized (mAudioPolicies) {
             for (AudioPolicyProxy policy : mAudioPolicies.values()) {
-                policy.connectMixes();
+                final int status = policy.connectMixes();
+                if (status != AudioSystem.SUCCESS) {
+                    // note that PERMISSION_DENIED may also indicate trouble getting to APService
+                    Log.e(TAG, "onAudioServerDied: error "
+                            + AudioSystem.audioSystemErrorToString(status)
+                            + " when connecting mixes for policy " + policy.toLogFriendlyString());
+                    policy.release();
+                }
             }
         }
 
@@ -7019,16 +7026,8 @@
         }
 
         public void binderDied() {
-            synchronized (mAudioPolicies) {
-                Log.i(TAG, "audio policy " + mPolicyCallback + " died");
-                release();
-                mAudioPolicies.remove(mPolicyCallback.asBinder());
-            }
-            if (mIsVolumeController) {
-                synchronized (mExtVolumeControllerLock) {
-                    mExtVolumeController = null;
-                }
-            }
+            Log.i(TAG, "audio policy " + mPolicyCallback + " died");
+            release();
         }
 
         String getRegistrationId() {
@@ -7052,9 +7051,20 @@
                     Log.e(TAG, "Fail to unregister Audiopolicy callback from MediaProjection");
                 }
             }
+            if (mIsVolumeController) {
+                synchronized (mExtVolumeControllerLock) {
+                    mExtVolumeController = null;
+                }
+            }
             final long identity = Binder.clearCallingIdentity();
             AudioSystem.registerPolicyMixes(mMixes, false);
             Binder.restoreCallingIdentity(identity);
+            synchronized (mAudioPolicies) {
+                mAudioPolicies.remove(mPolicyCallback.asBinder());
+            }
+            try {
+                mPolicyCallback.notifyUnregistration();
+            } catch (RemoteException e) { }
         }
 
         boolean hasMixAffectingUsage(int usage, int excludedFlags) {
@@ -7105,7 +7115,7 @@
             }
         }
 
-        int connectMixes() {
+        @AudioSystem.AudioSystemError int connectMixes() {
             final long identity = Binder.clearCallingIdentity();
             int status = AudioSystem.registerPolicyMixes(mMixes, true);
             Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 4f1db3c..619c21e 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -37,13 +37,12 @@
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
-import android.hardware.face.FaceManager;
 import android.hardware.face.IFaceService;
-import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IFingerprintService;
 import android.net.Uri;
 import android.os.Binder;
@@ -68,6 +67,8 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.server.SystemService;
+import com.android.server.biometrics.face.FaceAuthenticator;
+import com.android.server.biometrics.fingerprint.FingerprintAuthenticator;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -95,11 +96,6 @@
     private static final int MSG_CANCEL_AUTHENTICATION = 10;
     private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11;
     private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12;
-    private static final int[] FEATURE_ID = {
-        TYPE_FINGERPRINT,
-        TYPE_IRIS,
-        TYPE_FACE
-    };
 
     /**
      * Authentication either just called and we have not transitioned to the CALLED state, or
@@ -172,7 +168,7 @@
         byte[] mTokenEscrow;
         // Waiting for SystemUI to complete animation
         int mErrorEscrow;
-        String mErrorStringEscrow;
+        int mVendorCodeEscrow;
 
         // Timestamp when authentication started
         private long mStartTimeMs;
@@ -219,19 +215,15 @@
     private final Injector mInjector;
     @VisibleForTesting
     final IBiometricService.Stub mImpl;
+    private final boolean mHasFeatureFace;
     private final boolean mHasFeatureFingerprint;
     private final boolean mHasFeatureIris;
-    private final boolean mHasFeatureFace;
     @VisibleForTesting
     final SettingObserver mSettingObserver;
     private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
     private final Random mRandom = new Random();
 
     @VisibleForTesting
-    IFingerprintService mFingerprintService;
-    @VisibleForTesting
-    IFaceService mFaceService;
-    @VisibleForTesting
     IStatusBarService mStatusBarService;
     @VisibleForTesting
     KeyStore mKeyStore;
@@ -262,7 +254,7 @@
                 }
 
                 case MSG_ON_AUTHENTICATION_REJECTED: {
-                    handleAuthenticationRejected((String) msg.obj /* failureReason */);
+                    handleAuthenticationRejected();
                     break;
                 }
 
@@ -270,8 +262,9 @@
                     SomeArgs args = (SomeArgs) msg.obj;
                     handleOnError(
                             args.argi1 /* cookie */,
-                            args.argi2 /* error */,
-                            (String) args.arg1 /* message */);
+                            args.argi2 /* modality */,
+                            args.argi3 /* error */,
+                            args.argi4 /* vendorCode */);
                     args.recycle();
                     break;
                 }
@@ -331,7 +324,12 @@
                 }
 
                 case MSG_ON_AUTHENTICATION_TIMED_OUT: {
-                    handleAuthenticationTimedOut((String) msg.obj /* errorMessage */);
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleAuthenticationTimedOut(
+                            args.argi1 /* modality */,
+                            args.argi2 /* error */,
+                            args.argi3 /* vendorCode */);
+                    args.recycle();
                     break;
                 }
 
@@ -347,21 +345,23 @@
         }
     };
 
-    private final class AuthenticatorWrapper {
-        final int mType;
-        final BiometricAuthenticator mAuthenticator;
+    /**
+     * Wraps IBiometricAuthenticator implementation and stores information about the authenticator.
+     * TODO(b/141025588): Consider refactoring the tests to not rely on this implementation detail.
+     */
+    @VisibleForTesting
+    public static final class AuthenticatorWrapper {
+        public final int id;
+        public final int strength;
+        public final int modality;
+        public final IBiometricAuthenticator impl;
 
-        AuthenticatorWrapper(int type, BiometricAuthenticator authenticator) {
-            mType = type;
-            mAuthenticator = authenticator;
-        }
-
-        int getType() {
-            return mType;
-        }
-
-        BiometricAuthenticator getAuthenticator() {
-            return mAuthenticator;
+        AuthenticatorWrapper(int id, int strength, int modality,
+                IBiometricAuthenticator impl) {
+            this.id = id;
+            this.strength = strength;
+            this.modality = modality;
+            this.impl = impl;
         }
     }
 
@@ -521,23 +521,28 @@
         @Override
         public void onAuthenticationFailed()
                 throws RemoteException {
-            String failureReason = getContext().getString(R.string.biometric_not_recognized);
-            Slog.v(TAG, "onAuthenticationFailed: " + failureReason);
-            mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED, failureReason).sendToTarget();
+            Slog.v(TAG, "onAuthenticationFailed");
+            mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget();
         }
 
         @Override
-        public void onError(int cookie, int error, String message) throws RemoteException {
+        public void onError(int cookie, int modality, int error, int vendorCode)
+                throws RemoteException {
             // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are
             // soft errors and we should allow the user to try authenticating again instead of
             // dismissing BiometricPrompt.
             if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) {
-                mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, message).sendToTarget();
+                SomeArgs args = SomeArgs.obtain();
+                args.argi1 = modality;
+                args.argi2 = error;
+                args.argi3 = vendorCode;
+                mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, args).sendToTarget();
             } else {
                 SomeArgs args = SomeArgs.obtain();
                 args.argi1 = cookie;
-                args.argi2 = error;
-                args.arg1 = message;
+                args.argi2 = modality;
+                args.argi3 = error;
+                args.argi4 = vendorCode;
                 mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget();
             }
         }
@@ -666,7 +671,8 @@
             final long ident = Binder.clearCallingIdentity();
             int error;
             try {
-                final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
+                final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
+                        opPackageName);
                 error = result.second;
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -675,22 +681,30 @@
         }
 
         @Override
-        public boolean hasEnrolledBiometrics(int userId) {
+        public boolean hasEnrolledBiometrics(int userId, String opPackageName) {
             checkInternalPermission();
 
             final long ident = Binder.clearCallingIdentity();
             try {
-                for (int i = 0; i < mAuthenticators.size(); i++) {
-                    if (mAuthenticators.get(i).mAuthenticator.hasEnrolledTemplates(userId)) {
+                for (AuthenticatorWrapper authenticator : mAuthenticators) {
+                    if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
                         return true;
                     }
                 }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
             return false;
         }
 
+        @Override
+        public void registerAuthenticator(int id, int strength, int modality,
+                IBiometricAuthenticator authenticator) {
+            mAuthenticators.add(new AuthenticatorWrapper(id, strength, modality, authenticator));
+        }
+
         @Override // Binder call
         public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback)
                 throws RemoteException {
@@ -710,9 +724,11 @@
             checkInternalPermission();
             final long ident = Binder.clearCallingIdentity();
             try {
-                for (int i = 0; i < mAuthenticators.size(); i++) {
-                    mAuthenticators.get(i).getAuthenticator().setActiveUser(userId);
+                for (AuthenticatorWrapper authenticator : mAuthenticators) {
+                    authenticator.impl.setActiveUser(userId);
                 }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -723,11 +739,8 @@
             checkInternalPermission();
             final long ident = Binder.clearCallingIdentity();
             try {
-                if (mFingerprintService != null) {
-                    mFingerprintService.resetTimeout(token);
-                }
-                if (mFaceService != null) {
-                    mFaceService.resetLockout(token);
+                for (AuthenticatorWrapper authenticator : mAuthenticators) {
+                    authenticator.impl.resetLockout(token);
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote exception", e);
@@ -750,40 +763,69 @@
         }
     }
 
+    /**
+     * Class for injecting dependencies into BiometricService.
+     * TODO(b/141025588): Replace with a dependency injection framework (e.g. Guice, Dagger).
+     */
     @VisibleForTesting
-    static class Injector {
-        IActivityManager getActivityManagerService() {
+    public static class Injector {
+
+        @VisibleForTesting
+        public IActivityManager getActivityManagerService() {
             return ActivityManager.getService();
         }
 
-        IStatusBarService getStatusBarService() {
+        @VisibleForTesting
+        public IStatusBarService getStatusBarService() {
             return IStatusBarService.Stub.asInterface(
                     ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         }
 
-        IFingerprintService getFingerprintService() {
-            return IFingerprintService.Stub.asInterface(
-                    ServiceManager.getService(Context.FINGERPRINT_SERVICE));
+        /**
+         * Allows to mock FaceAuthenticator for testing.
+         */
+        @VisibleForTesting
+        public IBiometricAuthenticator getFingerprintAuthenticator() {
+            return new FingerprintAuthenticator(IFingerprintService.Stub.asInterface(
+                    ServiceManager.getService(Context.FINGERPRINT_SERVICE)));
         }
 
-        IFaceService getFaceService() {
-            return IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE));
+        /**
+         * Allows to mock FaceAuthenticator for testing.
+         */
+        @VisibleForTesting
+        public IBiometricAuthenticator getFaceAuthenticator() {
+            return new FaceAuthenticator(
+                    IFaceService.Stub.asInterface(ServiceManager.getService(Context.FACE_SERVICE)));
         }
 
-        SettingObserver getSettingObserver(Context context, Handler handler,
+        /**
+         * Allows to mock SettingObserver for testing.
+         */
+        @VisibleForTesting
+        public SettingObserver getSettingObserver(Context context, Handler handler,
                 List<EnabledOnKeyguardCallback> callbacks) {
             return new SettingObserver(context, handler, callbacks);
         }
 
-        KeyStore getKeyStore() {
+        @VisibleForTesting
+        public KeyStore getKeyStore() {
             return KeyStore.getInstance();
         }
 
-        boolean isDebugEnabled(Context context, int userId) {
+        /**
+         * Allows to enable/disable debug logs.
+         */
+        @VisibleForTesting
+        public boolean isDebugEnabled(Context context, int userId) {
             return Utils.isDebugEnabled(context, userId);
         }
 
-        void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
+        /**
+         * Allows to stub publishBinderService(...) for testing.
+         */
+        @VisibleForTesting
+        public void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
             service.publishBinderService(Context.BIOMETRIC_SERVICE, impl);
         }
     }
@@ -812,9 +854,9 @@
                 mEnabledOnKeyguardCallbacks);
 
         final PackageManager pm = context.getPackageManager();
+        mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
         mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
         mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
-        mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
 
         try {
             injector.getActivityManagerService().registerUserSwitchObserver(
@@ -833,26 +875,30 @@
 
     @Override
     public void onStart() {
-        // TODO: maybe get these on-demand
-        if (mHasFeatureFingerprint) {
-            mFingerprintService = mInjector.getFingerprintService();
-        }
-        if (mHasFeatureFace) {
-            mFaceService = mInjector.getFaceService();
+        // TODO(b/141025588): remove this code block once AuthService is integrated.
+        {
+            if (mHasFeatureFace) {
+                try {
+                    mImpl.registerAuthenticator(0, 0, TYPE_FACE, mInjector.getFaceAuthenticator());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+            if (mHasFeatureFingerprint) {
+                try {
+                    mImpl.registerAuthenticator(0, 0, TYPE_FINGERPRINT,
+                            mInjector.getFingerprintAuthenticator());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
+                }
+            }
+            if (mHasFeatureIris) {
+                Slog.e(TAG, "Iris is not supported");
+            }
         }
 
         mKeyStore = mInjector.getKeyStore();
         mStatusBarService = mInjector.getStatusBarService();
-
-        // Cache the authenticators
-        for (int featureId : FEATURE_ID) {
-            if (hasFeature(featureId)) {
-                AuthenticatorWrapper authenticator =
-                        new AuthenticatorWrapper(featureId, getAuthenticator(featureId));
-                mAuthenticators.add(authenticator);
-            }
-        }
-
         mInjector.publishBinderService(this, mImpl);
     }
 
@@ -868,7 +914,7 @@
      * {@link BiometricAuthenticator#TYPE_FACE}
      * and the error containing one of the {@link BiometricConstants} errors.
      */
-    private Pair<Integer, Integer> checkAndGetBiometricModality(int userId) {
+    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);
@@ -886,23 +932,26 @@
 
         int modality = TYPE_NONE;
         int firstHwAvailable = TYPE_NONE;
-        for (AuthenticatorWrapper authenticatorWrapper : mAuthenticators) {
-            modality = authenticatorWrapper.getType();
-            BiometricAuthenticator authenticator = authenticatorWrapper.getAuthenticator();
-            if (authenticator.isHardwareDetected()) {
-                isHardwareDetected = true;
-                if (firstHwAvailable == TYPE_NONE) {
-                    // Store the first one since we want to return the error in correct priority
-                    // order.
-                    firstHwAvailable = modality;
-                }
-                if (authenticator.hasEnrolledTemplates(userId)) {
-                    hasTemplatesEnrolled = true;
-                    if (isEnabledForApp(modality, userId)) {
-                        enabledForApps = true;
-                        break;
+        for (AuthenticatorWrapper authenticator : mAuthenticators) {
+            modality = authenticator.modality;
+            try {
+                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.
+                        firstHwAvailable = modality;
+                    }
+                    if (authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
+                        hasTemplatesEnrolled = true;
+                        if (isEnabledForApp(modality, userId)) {
+                            enabledForApps = true;
+                            break;
+                        }
                     }
                 }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception", e);
             }
         }
 
@@ -926,7 +975,7 @@
     }
 
     private boolean isEnabledForApp(int modality, int userId) {
-        switch(modality) {
+        switch (modality) {
             case TYPE_FINGERPRINT:
                 return true;
             case TYPE_IRIS:
@@ -939,47 +988,16 @@
         }
     }
 
-    private String getErrorString(int type, int error, int vendorCode) {
-        switch (type) {
-            case TYPE_FINGERPRINT:
-                return FingerprintManager.getErrorString(getContext(), error, vendorCode);
-            case TYPE_IRIS:
-                Slog.w(TAG, "Modality not supported");
-                return null; // not supported
-            case TYPE_FACE:
-                return FaceManager.getErrorString(getContext(), error, vendorCode);
-            default:
-                Slog.w(TAG, "Unable to get error string for modality: " + type);
-                return null;
+    private String getErrorString(int modality, int error, int vendorCode) {
+        for (AuthenticatorWrapper authenticator : mAuthenticators) {
+            if (authenticator.modality == modality) {
+                // TODO(b/141025588): Refactor IBiometricServiceReceiver.aidl#onError(...) to not
+                // ask for a String error message, but derive it from the error code instead.
+                return "";
+            }
         }
-    }
-
-    private BiometricAuthenticator getAuthenticator(int type) {
-        switch (type) {
-            case TYPE_FINGERPRINT:
-                return (FingerprintManager)
-                        getContext().getSystemService(Context.FINGERPRINT_SERVICE);
-            case TYPE_IRIS:
-                return null;
-            case TYPE_FACE:
-                return (FaceManager)
-                        getContext().getSystemService(Context.FACE_SERVICE);
-            default:
-                return null;
-        }
-    }
-
-    private boolean hasFeature(int type) {
-        switch (type) {
-            case TYPE_FINGERPRINT:
-                return mHasFeatureFingerprint;
-            case TYPE_IRIS:
-                return mHasFeatureIris;
-            case TYPE_FACE:
-                return mHasFeatureFace;
-            default:
-                return false;
-        }
+        Slog.w(TAG, "Unable to get error string for modality: " + modality);
+        return null;
     }
 
     private void logDialogDismissed(int reason) {
@@ -1081,14 +1099,14 @@
 
             // Notify SysUI that the biometric has been authenticated. SysUI already knows
             // the implicit/explicit state and will react accordingly.
-            mStatusBarService.onBiometricAuthenticated(true, null /* failureReason */);
+            mStatusBarService.onBiometricAuthenticated();
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
     }
 
-    private void handleAuthenticationRejected(String failureReason) {
-        Slog.v(TAG, "handleAuthenticationRejected: " + failureReason);
+    private void handleAuthenticationRejected() {
+        Slog.v(TAG, "handleAuthenticationRejected()");
         try {
             // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
             // after user dismissed/canceled dialog).
@@ -1097,7 +1115,8 @@
                 return;
             }
 
-            mStatusBarService.onBiometricAuthenticated(false, failureReason);
+            mStatusBarService.onBiometricError(TYPE_NONE,
+                    BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */);
 
             // TODO: This logic will need to be updated if BP is multi-modal
             if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
@@ -1112,8 +1131,9 @@
         }
     }
 
-    private void handleAuthenticationTimedOut(String message) {
-        Slog.v(TAG, "handleAuthenticationTimedOut: " + message);
+    private void handleAuthenticationTimedOut(int modality, int error, int vendorCode) {
+        Slog.v(TAG, String.format("handleAuthenticationTimedOut(%d, %d, %d)", modality, error,
+                vendorCode));
         try {
             // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded
             // after user dismissed/canceled dialog).
@@ -1122,14 +1142,14 @@
                 return;
             }
 
-            mStatusBarService.onBiometricAuthenticated(false, message);
+            mStatusBarService.onBiometricError(modality, error, vendorCode);
             mCurrentAuthSession.mState = STATE_AUTH_PAUSED;
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
     }
 
-    private void handleOnError(int cookie, int error, String message) {
+    private void handleOnError(int cookie, int modality, int error, int vendorCode) {
         Slog.d(TAG, "handleOnError: " + error + " cookie: " + cookie);
         // Errors can either be from the current auth session or the pending auth session.
         // The pending auth session may receive errors such as ERROR_LOCKOUT before
@@ -1140,7 +1160,7 @@
         try {
             if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
                 mCurrentAuthSession.mErrorEscrow = error;
-                mCurrentAuthSession.mErrorStringEscrow = message;
+                mCurrentAuthSession.mVendorCodeEscrow = vendorCode;
 
                 if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
                     final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
@@ -1148,20 +1168,20 @@
                     if (mCurrentAuthSession.isAllowDeviceCredential() && errorLockout) {
                         // SystemUI handles transition from biometric to device credential.
                         mCurrentAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
-                        mStatusBarService.onBiometricError(error, message);
+                        mStatusBarService.onBiometricError(modality, error, vendorCode);
                     } else {
                         mCurrentAuthSession.mState = STATE_ERROR_PENDING_SYSUI;
                         if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
                             mStatusBarService.hideAuthenticationDialog();
                         } else {
-                            mStatusBarService.onBiometricError(error, message);
+                            mStatusBarService.onBiometricError(modality, error, vendorCode);
                         }
                     }
                 } else if (mCurrentAuthSession.mState == STATE_AUTH_PAUSED) {
                     // In the "try again" state, we should forward canceled errors to
                     // the client and and clean up. The only error we should get here is
                     // ERROR_CANCELED due to another client kicking us out.
-                    mCurrentAuthSession.mClientReceiver.onError(error, message);
+                    mCurrentAuthSession.mClientReceiver.onError(modality, error, vendorCode);
                     mStatusBarService.hideAuthenticationDialog();
                     mCurrentAuthSession = null;
                 } else if (mCurrentAuthSession.mState == STATE_SHOWING_DEVICE_CREDENTIAL) {
@@ -1197,7 +1217,7 @@
                                 mCurrentAuthSession.mUserId,
                                 mCurrentAuthSession.mOpPackageName);
                     } else {
-                        mPendingAuthSession.mClientReceiver.onError(error, message);
+                        mPendingAuthSession.mClientReceiver.onError(modality, error, vendorCode);
                         mPendingAuthSession = null;
                     }
                 } else {
@@ -1261,8 +1281,10 @@
 
                 case BiometricPrompt.DISMISSED_REASON_USER_CANCEL:
                     mCurrentAuthSession.mClientReceiver.onError(
+                            mCurrentAuthSession.mModality,
                             BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED,
-                            getContext().getString(R.string.biometric_error_user_canceled));
+                            0 /* vendorCode */
+                    );
                     // Cancel authentication. Skip the token/package check since we are cancelling
                     // from system server. The interface is permission protected so this is fine.
                     cancelInternal(null /* token */, null /* package */, false /* fromClient */);
@@ -1270,8 +1292,11 @@
 
                 case BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED:
                 case BiometricPrompt.DISMISSED_REASON_ERROR:
-                    mCurrentAuthSession.mClientReceiver.onError(mCurrentAuthSession.mErrorEscrow,
-                            mCurrentAuthSession.mErrorStringEscrow);
+                    mCurrentAuthSession.mClientReceiver.onError(
+                            mCurrentAuthSession.mModality,
+                            mCurrentAuthSession.mErrorEscrow,
+                            mCurrentAuthSession.mVendorCodeEscrow
+                    );
                     break;
 
                 default:
@@ -1354,30 +1379,35 @@
             mPendingAuthSession = null;
 
             mCurrentAuthSession.mState = STATE_AUTH_STARTED;
-            try {
-                int modality = TYPE_NONE;
-                it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
-                while (it.hasNext()) {
-                    Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
-                    if (pair.getKey() == TYPE_FINGERPRINT) {
-                        mFingerprintService.startPreparedClient(pair.getValue());
-                    } else if (pair.getKey() == TYPE_IRIS) {
-                        Slog.e(TAG, "Iris unsupported");
-                    } else if (pair.getKey() == TYPE_FACE) {
-                        mFaceService.startPreparedClient(pair.getValue());
-                    } else {
-                        Slog.e(TAG, "Unknown modality: " + pair.getKey());
+            int modality = TYPE_NONE;
+            it = mCurrentAuthSession.mModalitiesMatched.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<Integer, Integer> pair = (Map.Entry) it.next();
+                boolean foundAuthenticator = false;
+                for (AuthenticatorWrapper authenticator : mAuthenticators) {
+                    if (authenticator.modality == pair.getKey()) {
+                        foundAuthenticator = true;
+                        try {
+                            authenticator.impl.startPreparedClient(pair.getValue());
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Remote exception", e);
+                        }
                     }
-                    modality |= pair.getKey();
                 }
+                if (!foundAuthenticator) {
+                    Slog.e(TAG, "Unknown modality: " + pair.getKey());
+                }
+                modality |= pair.getKey();
+            }
 
-                if (!continuing) {
+            if (!continuing) {
+                try {
                     mStatusBarService.showAuthenticationDialog(mCurrentAuthSession.mBundle,
                             mInternalReceiver, modality, requireConfirmation, userId,
                             mCurrentAuthSession.mOpPackageName);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception", e);
                 }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception", e);
             }
         }
     }
@@ -1387,7 +1417,8 @@
             int callingUid, int callingPid, int callingUserId) {
 
         mHandler.post(() -> {
-            final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
+            final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId,
+                    opPackageName);
             final int modality = result.first;
             final int error = result.second;
 
@@ -1400,23 +1431,7 @@
             } else if (error != BiometricConstants.BIOMETRIC_SUCCESS) {
                 // Check for errors, notify callback, and return
                 try {
-                    final String hardwareUnavailable =
-                            getContext().getString(R.string.biometric_error_hw_unavailable);
-                    switch (error) {
-                        case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
-                            receiver.onError(error, hardwareUnavailable);
-                            break;
-                        case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
-                            receiver.onError(error, hardwareUnavailable);
-                            break;
-                        case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
-                            receiver.onError(error,
-                                    getErrorString(modality, error, 0 /* vendorCode */));
-                            break;
-                        default:
-                            Slog.e(TAG, "Unhandled error");
-                            break;
-                    }
+                    receiver.onError(modality, error, 0 /* vendorCode */);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to send error", e);
                 }
@@ -1435,41 +1450,41 @@
      * modality/modalities to start authenticating with. authenticateInternal() should only be
      * used for:
      * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is,
-     *    invoked, shortly after which BiometricPrompt is shown and authentication starts
+     * invoked, shortly after which BiometricPrompt is shown and authentication starts
      * 2) Preparing <Biometric>Services for authentication when BiometricPrompt is already shown
-     *    and the user has pressed "try again"
+     * and the user has pressed "try again"
      */
     private void authenticateInternal(IBinder token, long sessionId, int userId,
             IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
             int callingUid, int callingPid, int callingUserId, int modality) {
+        boolean requireConfirmation = bundle.getBoolean(
+                BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
+        if ((modality & TYPE_FACE) != 0) {
+            // Check if the user has forced confirmation to be required in Settings.
+            requireConfirmation = requireConfirmation
+                    || mSettingObserver.getFaceAlwaysRequireConfirmation(userId);
+        }
+        // Generate random cookies to pass to the services that should prepare to start
+        // authenticating. Store the cookie here and wait for all services to "ack"
+        // 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);
+        Slog.d(TAG, "Creating auth session. Modality: " + modality
+                + ", cookie: " + cookie
+                + ", authenticators: " + authenticators);
+        final HashMap<Integer, Integer> modalities = new HashMap<>();
+
+        // 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) {
+            modalities.put(modality, cookie);
+        }
+        mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
+                receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
+                modality, requireConfirmation);
+
         try {
-            boolean requireConfirmation = bundle.getBoolean(
-                    BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
-            if ((modality & TYPE_FACE) != 0) {
-                // Check if the user has forced confirmation to be required in Settings.
-                requireConfirmation = requireConfirmation
-                        || mSettingObserver.getFaceAlwaysRequireConfirmation(userId);
-            }
-            // Generate random cookies to pass to the services that should prepare to start
-            // authenticating. Store the cookie here and wait for all services to "ack"
-            // 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);
-            Slog.d(TAG, "Creating auth session. Modality: " + modality
-                    + ", cookie: " + cookie
-                    + ", authenticators: " + authenticators);
-            final HashMap<Integer, Integer> modalities = new HashMap<>();
-
-            // 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) {
-                modalities.put(modality, cookie);
-            }
-            mPendingAuthSession = new AuthSession(modalities, token, sessionId, userId,
-                    receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
-                    modality, requireConfirmation);
-
             if (authenticators == Authenticator.TYPE_CREDENTIAL) {
                 mPendingAuthSession.mState = STATE_SHOWING_DEVICE_CREDENTIAL;
                 mCurrentAuthSession = mPendingAuthSession;
@@ -1484,19 +1499,10 @@
                         mCurrentAuthSession.mOpPackageName);
             } else {
                 mPendingAuthSession.mState = STATE_AUTH_CALLED;
-                // No polymorphism :(
-                if ((modality & TYPE_FINGERPRINT) != 0) {
-                    mFingerprintService.prepareForAuthentication(token, sessionId, userId,
-                            mInternalReceiver, opPackageName, cookie,
-                            callingUid, callingPid, callingUserId);
-                }
-                if ((modality & TYPE_IRIS) != 0) {
-                    Slog.w(TAG, "Iris unsupported");
-                }
-                if ((modality & TYPE_FACE) != 0) {
-                    mFaceService.prepareForAuthentication(requireConfirmation,
-                            token, sessionId, userId, mInternalReceiver, opPackageName,
-                            cookie, callingUid, callingPid, callingUserId);
+                for (AuthenticatorWrapper authenticator : mAuthenticators) {
+                    authenticator.impl.prepareForAuthentication(requireConfirmation, token,
+                            sessionId, userId, mInternalReceiver, opPackageName, cookie, callingUid,
+                            callingPid, callingUserId);
                 }
             }
         } catch (RemoteException e) {
@@ -1517,11 +1523,10 @@
             try {
                 // Send error to client
                 mCurrentAuthSession.mClientReceiver.onError(
+                        mCurrentAuthSession.mModality,
                         BiometricConstants.BIOMETRIC_ERROR_CANCELED,
-                        getContext().getString(
-                                com.android.internal.R.string.biometric_error_user_canceled)
+                        0 /* vendorCode */
                 );
-
                 mCurrentAuthSession = null;
                 mStatusBarService.hideAuthenticationDialog();
             } catch (RemoteException e) {
@@ -1537,30 +1542,25 @@
         final int callingPid = Binder.getCallingPid();
         final int callingUserId = UserHandle.getCallingUserId();
 
-        try {
-            if (mCurrentAuthSession == null) {
-                Slog.w(TAG, "Skipping cancelInternal");
-                return;
-            } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
-                Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
-                return;
-            }
+        if (mCurrentAuthSession == null) {
+            Slog.w(TAG, "Skipping cancelInternal");
+            return;
+        } else if (mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+            Slog.w(TAG, "Skipping cancelInternal, state: " + mCurrentAuthSession.mState);
+            return;
+        }
 
-            // TODO: For multiple modalities, send a single ERROR_CANCELED only when all
-            // drivers have canceled authentication.
-            if ((mCurrentAuthSession.mModality & TYPE_FINGERPRINT) != 0) {
-                mFingerprintService.cancelAuthenticationFromService(token, opPackageName,
-                        callingUid, callingPid, callingUserId, fromClient);
+        // TODO: For multiple modalities, send a single ERROR_CANCELED only when all
+        // drivers have canceled authentication.
+        for (AuthenticatorWrapper authenticator : mAuthenticators) {
+            if ((authenticator.modality & mCurrentAuthSession.mModality) != 0) {
+                try {
+                    authenticator.impl.cancelAuthenticationFromService(token, opPackageName,
+                            callingUid, callingPid, callingUserId, fromClient);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to cancel authentication");
+                }
             }
-            if ((mCurrentAuthSession.mModality & TYPE_IRIS) != 0) {
-                Slog.w(TAG, "Iris unsupported");
-            }
-            if ((mCurrentAuthSession.mModality & TYPE_FACE) != 0) {
-                mFaceService.cancelAuthenticationFromService(token, opPackageName,
-                        callingUid, callingPid, callingUserId, fromClient);
-            }
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Unable to cancel authentication");
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java
new file mode 100644
index 0000000..5df5b29d
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/face/FaceAuthenticator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.face;
+
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.face.IFaceService;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * TODO(b/141025588): Add JavaDoc.
+ */
+public final class FaceAuthenticator extends IBiometricAuthenticator.Stub {
+    private final IFaceService mFaceService;
+
+    public FaceAuthenticator(IFaceService faceService) {
+        mFaceService = faceService;
+    }
+
+    @Override
+    public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
+            long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
+            String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
+            throws RemoteException {
+        mFaceService.prepareForAuthentication(requireConfirmation, token, sessionId, userId,
+                wrapperReceiver, opPackageName, cookie, callingUid, callingPid, callingUserId);
+    }
+
+    @Override
+    public void startPreparedClient(int cookie) throws RemoteException {
+        mFaceService.startPreparedClient(cookie);
+    }
+
+    @Override
+    public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
+            int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
+        mFaceService.cancelAuthenticationFromService(token, opPackageName, callingUid, callingPid,
+                callingUserId, fromClient);
+    }
+
+    @Override
+    public boolean isHardwareDetected(String opPackageName) throws RemoteException {
+        return mFaceService.isHardwareDetected(opPackageName);
+    }
+
+    @Override
+    public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
+        return mFaceService.hasEnrolledFaces(userId, opPackageName);
+    }
+
+    @Override
+    public void resetLockout(byte[] token) throws RemoteException {
+        mFaceService.resetLockout(token);
+    }
+
+    @Override
+    public void setActiveUser(int uid) throws RemoteException {
+        mFaceService.setActiveUser(uid);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 1b13212..a0c8e23 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.RESET_FACE_LOCKOUT;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
@@ -538,7 +539,7 @@
 
         // TODO: refactor out common code here
         @Override // Binder call
-        public boolean isHardwareDetected(long deviceId, String opPackageName) {
+        public boolean isHardwareDetected(String opPackageName) {
             checkPermission(USE_BIOMETRIC_INTERNAL);
             if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
                     Binder.getCallingUid(), Binder.getCallingPid(),
@@ -752,8 +753,7 @@
         public void onError(long deviceId, int error, int vendorCode, int cookie)
                 throws RemoteException {
             if (getWrapperReceiver() != null) {
-                getWrapperReceiver().onError(cookie, error,
-                        FaceManager.getErrorString(getContext(), error, vendorCode));
+                getWrapperReceiver().onError(cookie, TYPE_FACE, error, vendorCode);
             }
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
new file mode 100644
index 0000000..6150de1
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintAuthenticator.java
@@ -0,0 +1,75 @@
+/*
+ * 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.fingerprint;
+
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.fingerprint.IFingerprintService;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * TODO(b/141025588): Add JavaDoc.
+ */
+public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub {
+    private final IFingerprintService mFingerprintService;
+
+    public FingerprintAuthenticator(IFingerprintService fingerprintService) {
+        mFingerprintService = fingerprintService;
+    }
+
+    @Override
+    public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
+            long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
+            String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
+            throws RemoteException {
+        mFingerprintService.prepareForAuthentication(token, sessionId, userId, wrapperReceiver,
+                opPackageName, cookie, callingUid, callingPid, callingUserId);
+    }
+
+    @Override
+    public void startPreparedClient(int cookie) throws RemoteException {
+        mFingerprintService.startPreparedClient(cookie);
+    }
+
+    @Override
+    public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
+            int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
+        mFingerprintService.cancelAuthenticationFromService(token, opPackageName, callingUid,
+                callingPid, callingUserId, fromClient);
+    }
+
+    @Override
+    public boolean isHardwareDetected(String opPackageName) throws RemoteException {
+        return mFingerprintService.isHardwareDetected(opPackageName);
+    }
+
+    @Override
+    public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
+        return mFingerprintService.hasEnrolledFingerprints(userId, opPackageName);
+    }
+
+    @Override
+    public void resetLockout(byte[] token) throws RemoteException {
+        mFingerprintService.resetTimeout(token);
+    }
+
+    @Override
+    public void setActiveUser(int uid) throws RemoteException {
+        mFingerprintService.setActiveUser(uid);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index d85af2e..44797ad 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 import static android.Manifest.permission.USE_BIOMETRIC;
 import static android.Manifest.permission.USE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 
 import android.app.ActivityManager;
 import android.app.AlarmManager;
@@ -350,7 +351,7 @@
 
         // TODO: refactor out common code here
         @Override // Binder call
-        public boolean isHardwareDetected(long deviceId, String opPackageName) {
+        public boolean isHardwareDetected(String opPackageName) {
             if (!canUseBiometric(opPackageName, false /* foregroundOnly */,
                     Binder.getCallingUid(), Binder.getCallingPid(),
                     UserHandle.getCallingUserId())) {
@@ -480,8 +481,7 @@
         public void onError(long deviceId, int error, int vendorCode, int cookie)
                 throws RemoteException {
             if (getWrapperReceiver() != null) {
-                getWrapperReceiver().onError(cookie, error,
-                        FingerprintManager.getErrorString(getContext(), error, vendorCode));
+                getWrapperReceiver().onError(cookie, TYPE_FINGERPRINT, error, vendorCode);
             }
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java
new file mode 100644
index 0000000..c44b8e7
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/iris/IrisAuthenticator.java
@@ -0,0 +1,68 @@
+/*
+ * 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.iris;
+
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.IBiometricServiceReceiverInternal;
+import android.hardware.iris.IIrisService;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * TODO(b/141025588): Add JavaDoc.
+ */
+public final class IrisAuthenticator extends IBiometricAuthenticator.Stub {
+    private final IIrisService mIrisService;
+
+    public IrisAuthenticator(IIrisService irisService) {
+        mIrisService = irisService;
+    }
+
+    @Override
+    public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
+            long sessionId, int userId, IBiometricServiceReceiverInternal wrapperReceiver,
+            String opPackageName, int cookie, int callingUid, int callingPid, int callingUserId)
+            throws RemoteException {
+    }
+
+    @Override
+    public void startPreparedClient(int cookie) throws RemoteException {
+    }
+
+    @Override
+    public void cancelAuthenticationFromService(IBinder token, String opPackageName, int callingUid,
+            int callingPid, int callingUserId, boolean fromClient) throws RemoteException {
+    }
+
+    @Override
+    public boolean isHardwareDetected(String opPackageName) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean hasEnrolledTemplates(int userId, String opPackageName) throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public void resetLockout(byte[] token) throws RemoteException {
+    }
+
+    @Override
+    public void setActiveUser(int uid) throws RemoteException {
+    }
+}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 8a7dcc1..9ac9955 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.UserHandle;
 import android.util.Slog;
 import android.util.StatsLog;
 
@@ -53,8 +54,8 @@
     }
 
     @Override
-    public void reportChangeByPackageName(long changeId, String packageName) {
-        ApplicationInfo appInfo = getApplicationInfo(packageName);
+    public void reportChangeByPackageName(long changeId, String packageName, int userId) {
+        ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
         if (appInfo == null) {
             return;
         }
@@ -79,8 +80,8 @@
     }
 
     @Override
-    public boolean isChangeEnabledByPackageName(long changeId, String packageName) {
-        ApplicationInfo appInfo = getApplicationInfo(packageName);
+    public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) {
+        ApplicationInfo appInfo = getApplicationInfo(packageName, userId);
         if (appInfo == null) {
             return true;
         }
@@ -95,7 +96,8 @@
         }
         boolean enabled = true;
         for (String packageName : packages) {
-            enabled = enabled && isChangeEnabledByPackageName(changeId, packageName);
+            enabled = enabled && isChangeEnabledByPackageName(changeId, packageName,
+                    UserHandle.getUserId(uid));
         }
         return enabled;
     }
@@ -126,9 +128,9 @@
         mChangeReporter.resetReportedChanges(appInfo.uid);
     }
 
-    private ApplicationInfo getApplicationInfo(String packageName) {
+    private ApplicationInfo getApplicationInfo(String packageName, int userId) {
         try {
-            return mContext.getPackageManager().getApplicationInfo(packageName, 0);
+            return mContext.getPackageManager().getApplicationInfoAsUser(packageName, 0, userId);
         } catch (PackageManager.NameNotFoundException e) {
             Slog.e(TAG, "No installed package " + packageName);
         }
diff --git a/services/core/java/com/android/server/compat/PlatformCompatNative.java b/services/core/java/com/android/server/compat/PlatformCompatNative.java
new file mode 100644
index 0000000..85dfbf4
--- /dev/null
+++ b/services/core/java/com/android/server/compat/PlatformCompatNative.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.compat;
+
+import com.android.internal.compat.IPlatformCompatNative;
+
+/**
+ * @hide
+ */
+public class PlatformCompatNative extends IPlatformCompatNative.Stub {
+    private final PlatformCompat mPlatformCompat;
+
+    public PlatformCompatNative(PlatformCompat platformCompat) {
+        mPlatformCompat = platformCompat;
+    }
+
+    @Override
+    public void reportChangeByPackageName(long changeId, String packageName, int userId) {
+        mPlatformCompat.reportChangeByPackageName(changeId, packageName, userId);
+    }
+
+    @Override
+    public void reportChangeByUid(long changeId, int uid) {
+        mPlatformCompat.reportChangeByUid(changeId, uid);
+    }
+
+    @Override
+    public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) {
+        return mPlatformCompat.isChangeEnabledByPackageName(changeId, packageName, userId);
+    }
+
+    @Override
+    public boolean isChangeEnabledByUid(long changeId, int uid) {
+        return mPlatformCompat.isChangeEnabledByUid(changeId, uid);
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 077c405..d13e675 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -53,7 +53,8 @@
         NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
         LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN),
         PARTIAL_CONNECTIVITY(SystemMessage.NOTE_NETWORK_PARTIAL_CONNECTIVITY),
-        SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
+        SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN),
+        PRIVATE_DNS_BROKEN(SystemMessage.NOTE_NETWORK_PRIVATE_DNS_BROKEN);
 
         public final int eventId;
 
@@ -175,13 +176,23 @@
         }
 
         Resources r = Resources.getSystem();
-        CharSequence title;
-        CharSequence details;
+        final CharSequence title;
+        final CharSequence details;
         int icon = getIcon(transportType, notifyType);
         if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
             title = r.getString(R.string.wifi_no_internet,
                     WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
             details = r.getString(R.string.wifi_no_internet_detailed);
+        } else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) {
+            if (transportType == TRANSPORT_CELLULAR) {
+                title = r.getString(R.string.mobile_no_internet);
+            } else if (transportType == TRANSPORT_WIFI) {
+                title = r.getString(R.string.wifi_no_internet,
+                        WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
+            } else {
+                title = r.getString(R.string.other_networks_no_internet);
+            }
+            details = r.getString(R.string.private_dns_broken_detailed);
         } else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY
                 && transportType == TRANSPORT_WIFI) {
             title = r.getString(R.string.network_partial_connectivity,
@@ -357,8 +368,10 @@
         }
         switch (t) {
             case SIGN_IN:
-                return 5;
+                return 6;
             case PARTIAL_CONNECTIVITY:
+                return 5;
+            case PRIVATE_DNS_BROKEN:
                 return 4;
             case NO_INTERNET:
                 return 3;
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index 61e5d1f..75ba5b8 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -17,6 +17,7 @@
 package com.android.server.location;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 /**
  * Represents the calling process's uid, pid, and package name.
@@ -25,13 +26,15 @@
     public final int mUid;
     public final int mPid;
     public final String mPackageName;
+    public final @Nullable String mFeatureId;
     public final @NonNull String mListenerIdentifier;
 
-    public CallerIdentity(int uid, int pid, String packageName,
+    public CallerIdentity(int uid, int pid, String packageName, @Nullable String featureId,
             @NonNull String listenerIdentifier) {
         mUid = uid;
         mPid = pid;
         mPackageName = packageName;
+        mFeatureId = featureId;
         mListenerIdentifier = listenerIdentifier;
     }
 }
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
index 4f10a71..17a2169 100644
--- a/services/core/java/com/android/server/location/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -17,6 +17,7 @@
 package com.android.server.location;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.ContentResolver;
@@ -152,7 +153,7 @@
     }
 
     public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
-            int allowedResolutionLevel, int uid, String packageName,
+            int allowedResolutionLevel, int uid, String packageName, @Nullable String featureId,
             @NonNull String listenerIdentifier) {
         if (D) {
             Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
@@ -160,8 +161,8 @@
         }
 
         GeofenceState state = new GeofenceState(geofence,
-                request.getExpireAt(), allowedResolutionLevel, uid, packageName, listenerIdentifier,
-                intent);
+                request.getExpireAt(), allowedResolutionLevel, uid, packageName, featureId,
+                listenerIdentifier, intent);
         synchronized (mLock) {
             // first make sure it doesn't already exist
             for (int i = mFences.size() - 1; i >= 0; i--) {
@@ -304,7 +305,7 @@
                 int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
                 if (op >= 0) {
                     if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
-                            state.mPackageName, null, state.mListenerIdentifier)
+                            state.mPackageName, state.mFeatureId, state.mListenerIdentifier)
                             != AppOpsManager.MODE_ALLOWED) {
                         if (D) {
                             Slog.d(TAG, "skipping geofence processing for no op app: "
diff --git a/services/core/java/com/android/server/location/GeofenceState.java b/services/core/java/com/android/server/location/GeofenceState.java
index fe0719d..a91a1dc 100644
--- a/services/core/java/com/android/server/location/GeofenceState.java
+++ b/services/core/java/com/android/server/location/GeofenceState.java
@@ -18,6 +18,7 @@
 package com.android.server.location;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.location.Geofence;
 import android.location.Location;
@@ -39,6 +40,7 @@
     public final int mAllowedResolutionLevel;
     public final int mUid;
     public final String mPackageName;
+    public final @Nullable String mFeatureId;
     public final @NonNull String mListenerIdentifier;
     public final PendingIntent mIntent;
 
@@ -46,7 +48,8 @@
     double mDistanceToCenter;  // current distance to center of fence
 
     public GeofenceState(Geofence fence, long expireAt, int allowedResolutionLevel, int uid,
-            String packageName, @NonNull String listenerIdentifier, PendingIntent intent) {
+            String packageName, @Nullable String featureId, @NonNull String listenerIdentifier,
+            PendingIntent intent) {
         mState = STATE_UNKNOWN;
         mDistanceToCenter = Double.MAX_VALUE;
 
@@ -55,6 +58,7 @@
         mAllowedResolutionLevel = allowedResolutionLevel;
         mUid = uid;
         mPackageName = packageName;
+        mFeatureId = featureId;
         mListenerIdentifier = listenerIdentifier;
         mIntent = intent;
 
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index 25b544f..60ce1f4 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -182,7 +182,7 @@
         }
 
         return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.mUid,
-                callerIdentity.mPackageName, null,
+                callerIdentity.mPackageName, callerIdentity.mFeatureId,
                 "Location sent to " + callerIdentity.mListenerIdentifier)
                 == AppOpsManager.MODE_ALLOWED;
     }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 34fb641..9bbe628 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -23,8 +23,11 @@
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
+import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -101,7 +104,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -116,7 +118,6 @@
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.ILockSettings;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternUtils.CredentialType;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
@@ -178,7 +179,6 @@
 
     private static final int PROFILE_KEY_IV_SIZE = 12;
     private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
-    private static final int SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT = 1;
     private static final String PREV_SYNTHETIC_PASSWORD_HANDLE_KEY = "prev-sp-handle";
     private static final String SYNTHETIC_PASSWORD_UPDATE_TIME_KEY = "sp-handle-ts";
 
@@ -317,14 +317,34 @@
         }
     }
 
+    private LockscreenCredential generateRandomProfilePassword() {
+        byte[] randomLockSeed = new byte[] {};
+        try {
+            randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
+            char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
+            byte[] newPassword = new byte[newPasswordChars.length];
+            for (int i = 0; i < newPasswordChars.length; i++) {
+                newPassword[i] = (byte) newPasswordChars[i];
+            }
+            LockscreenCredential credential =
+                    LockscreenCredential.createManagedPassword(newPassword);
+            Arrays.fill(newPasswordChars, '\u0000');
+            Arrays.fill(newPassword, (byte) 0);
+            Arrays.fill(randomLockSeed, (byte) 0);
+            return credential;
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException("Fail to generate profile password", e);
+        }
+    }
+
     /**
      * Tie managed profile to primary profile if it is in unified mode and not tied before.
      *
      * @param managedUserId Managed profile user Id
      * @param managedUserPassword Managed profile original password (when it has separated lock).
-     *            NULL when it does not have a separated lock before.
      */
-    public void tieManagedProfileLockIfNecessary(int managedUserId, byte[] managedUserPassword) {
+    public void tieManagedProfileLockIfNecessary(int managedUserId,
+            LockscreenCredential managedUserPassword) {
         if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId);
         // Only for managed profile
         if (!mUserManager.getUserInfo(managedUserId).isManagedProfile()) {
@@ -356,27 +376,10 @@
             return;
         }
         if (DEBUG) Slog.v(TAG, "Tie managed profile to parent now!");
-        byte[] randomLockSeed = new byte[] {};
-        try {
-            randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
-            char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
-            byte[] newPassword = new byte[newPasswordChars.length];
-            for (int i = 0; i < newPasswordChars.length; i++) {
-                newPassword[i] = (byte) newPasswordChars[i];
-            }
-            Arrays.fill(newPasswordChars, '\u0000');
-            final int quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-            setLockCredentialInternal(newPassword, CREDENTIAL_TYPE_PASSWORD, managedUserPassword,
-                    quality, managedUserId, false, /* isLockTiedToParent= */ true);
-            // We store a private credential for the managed user that's unlocked by the primary
-            // account holder's credential. As such, the user will never be prompted to enter this
-            // password directly, so we always store a password.
-            setLong(LockPatternUtils.PASSWORD_TYPE_KEY, quality, managedUserId);
-            tieProfileLockToParent(managedUserId, newPassword);
-            Arrays.fill(newPassword, (byte) 0);
-        } catch (NoSuchAlgorithmException e) {
-            Slog.e(TAG, "Fail to tie managed profile", e);
-            // Nothing client can do to fix this issue, so we do not throw exception out
+        try (LockscreenCredential unifiedProfilePassword = generateRandomProfilePassword()) {
+            setLockCredentialInternal(unifiedProfilePassword, managedUserPassword, managedUserId,
+                    false, /* isLockTiedToParent= */ true);
+            tieProfileLockToParent(managedUserId, unifiedProfilePassword);
         }
     }
 
@@ -512,6 +515,10 @@
             }
         }
 
+        public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
+                int defaultValue) {
+            return Settings.Global.getInt(contentResolver, keyName, defaultValue);
+        }
     }
 
     public LockSettingsService(Context context) {
@@ -682,7 +689,7 @@
                 hideEncryptionNotification(new UserHandle(userId));
 
                 if (mUserManager.getUserInfo(userId).isManagedProfile()) {
-                    tieManagedProfileLockIfNecessary(userId, null);
+                    tieManagedProfileLockIfNecessary(userId, LockscreenCredential.createNone());
                 }
 
                 // If the user doesn't have a credential, try and derive their secret for the
@@ -704,10 +711,9 @@
             }
 
             final long handle = getSyntheticPasswordHandleLocked(userId);
-            final byte[] noCredential = null;
             AuthenticationResult result =
-                    mSpManager.unwrapPasswordBasedSyntheticPassword(
-                            getGateKeeperService(), handle, noCredential, userId, null);
+                    mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
+                            handle, LockscreenCredential.createNone(), userId, null);
             if (result.authToken != null) {
                 Slog.i(TAG, "Retrieved auth token for user " + userId);
                 onAuthTokenKnownForUser(userId, result.authToken);
@@ -870,25 +876,6 @@
         final List<UserInfo> users = mUserManager.getUsers();
         for (int i = 0; i < users.size(); i++) {
             final UserInfo userInfo = users.get(i);
-            if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
-                // When managed profile has a unified lock, the password quality stored has 2
-                // possibilities only.
-                // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
-                // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
-                // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
-                // unified lock.
-                final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
-                if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
-                    // Only possible when it's upgraded from nyc dp3
-                    Slog.i(TAG, "Migrated tied profile lock type");
-                    setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                            DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
-                } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
-                    // It should not happen
-                    Slog.e(TAG, "Invalid tied profile lock type: " + quality);
-                }
-            }
             try {
                 final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
                 java.security.KeyStore keyStore =
@@ -1031,21 +1018,22 @@
 
     @Override
     public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
-            byte[] managedUserPassword) {
+            LockscreenCredential managedUserPassword) {
         checkWritePermission(userId);
         if (!mLockPatternUtils.hasSecureLockScreen()) {
             throw new UnsupportedOperationException(
                     "This operation requires secure lock screen feature.");
         }
         synchronized (mSeparateChallengeLock) {
-            setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword);
+            setSeparateProfileChallengeEnabledLocked(userId, enabled, managedUserPassword != null
+                    ? managedUserPassword : LockscreenCredential.createNone());
         }
         notifySeparateProfileChallengeChanged(userId);
     }
 
     @GuardedBy("mSeparateChallengeLock")
     private void setSeparateProfileChallengeEnabledLocked(@UserIdInt int userId,
-            boolean enabled, byte[] managedUserPassword) {
+            boolean enabled, LockscreenCredential managedUserPassword) {
         final boolean old = getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
         setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
         try {
@@ -1083,6 +1071,10 @@
     @Override
     public void setLong(String key, long value, int userId) {
         checkWritePermission(userId);
+        setLongUnchecked(key, value, userId);
+    }
+
+    private void setLongUnchecked(String key, long value, int userId) {
         setStringUnchecked(key, userId, Long.toString(value));
     }
 
@@ -1112,6 +1104,10 @@
     @Override
     public long getLong(String key, long defaultValue, int userId) {
         checkReadPermission(key, userId);
+        return getLongUnchecked(key, defaultValue, userId);
+    }
+
+    private long getLongUnchecked(String key, long defaultValue, int userId) {
         String value = getStringUnchecked(key, null, userId);
         return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
     }
@@ -1122,7 +1118,7 @@
         return getStringUnchecked(key, defaultValue, userId);
     }
 
-    public String getStringUnchecked(String key, String defaultValue, int userId) {
+    private String getStringUnchecked(String key, String defaultValue, int userId) {
         if (Settings.Secure.LOCK_PATTERN_ENABLED.equals(key)) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -1131,9 +1127,8 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
-
         if (userId == USER_FRP) {
-            return getFrpStringUnchecked(key);
+            return null;
         }
 
         if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) {
@@ -1143,51 +1138,81 @@
         return mStorage.readKeyValue(key, defaultValue, userId);
     }
 
-    private String getFrpStringUnchecked(String key) {
-        if (LockPatternUtils.PASSWORD_TYPE_KEY.equals(key)) {
-            return String.valueOf(readFrpPasswordQuality());
-        }
-        return null;
+    private void setKeyguardStoredQuality(int quality, int userId) {
+        if (DEBUG) Slog.d(TAG, "setKeyguardStoredQuality: user=" + userId + " quality=" + quality);
+        setLongUnchecked(LockPatternUtils.PASSWORD_TYPE_KEY, quality, userId);
     }
 
-    private int readFrpPasswordQuality() {
-        return mStorage.readPersistentDataBlock().qualityForUi;
+    private int getKeyguardStoredQuality(int userId) {
+        return (int) getLongUnchecked(LockPatternUtils.PASSWORD_TYPE_KEY,
+                DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
     }
 
     @Override
-    public boolean havePassword(int userId) {
+    public int getCredentialType(int userId) {
         checkPasswordHavePermission(userId);
-        synchronized (mSpManager) {
-            if (isSyntheticPasswordBasedCredentialLocked(userId)) {
-                final long handle = getSyntheticPasswordHandleLocked(userId);
-                return mSpManager.getCredentialType(handle, userId) == CREDENTIAL_TYPE_PASSWORD;
-            }
-        }
-        // Do we need a permissions check here?
-        return mStorage.hasPassword(userId);
+        return getCredentialTypeInternal(userId);
     }
 
-    @Override
-    public boolean havePattern(int userId) {
-        checkPasswordHavePermission(userId);
+    // TODO: this is a hot path, can we optimize it?
+    /**
+     * Returns the credential type of the user, can be one of {@link #CREDENTIAL_TYPE_NONE},
+     * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and
+     * {@link #CREDENTIAL_TYPE_PASSWORD}
+     */
+    public int getCredentialTypeInternal(int userId) {
+        if (userId == USER_FRP) {
+            return getFrpCredentialType();
+        }
         synchronized (mSpManager) {
             if (isSyntheticPasswordBasedCredentialLocked(userId)) {
                 final long handle = getSyntheticPasswordHandleLocked(userId);
-                return mSpManager.getCredentialType(handle, userId) == CREDENTIAL_TYPE_PATTERN;
+                int rawType = mSpManager.getCredentialType(handle, userId);
+                if (rawType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+                    return rawType;
+                }
+                return pinOrPasswordQualityToCredentialType(getKeyguardStoredQuality(userId));
             }
         }
-        // Do we need a permissions check here?
-        return mStorage.hasPattern(userId);
+        // Intentional duplication of the getKeyguardStoredQuality() call above since this is a
+        // unlikely code path (device with pre-synthetic password credential). We want to skip
+        // calling getKeyguardStoredQuality whenever possible.
+        final int savedQuality = getKeyguardStoredQuality(userId);
+        if (savedQuality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+                && mStorage.hasPattern(userId)) {
+            return CREDENTIAL_TYPE_PATTERN;
+        }
+        if (savedQuality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+                && mStorage.hasPassword(userId)) {
+            return pinOrPasswordQualityToCredentialType(savedQuality);
+        }
+        return CREDENTIAL_TYPE_NONE;
+    }
+
+    private int getFrpCredentialType() {
+        PersistentData data = mStorage.readPersistentDataBlock();
+        if (data.type != PersistentData.TYPE_SP && data.type != PersistentData.TYPE_SP_WEAVER) {
+            return CREDENTIAL_TYPE_NONE;
+        }
+        int credentialType = SyntheticPasswordManager.getFrpCredentialType(data.payload);
+        if (credentialType != CREDENTIAL_TYPE_PASSWORD_OR_PIN) {
+            return credentialType;
+        }
+        return pinOrPasswordQualityToCredentialType(data.qualityForUi);
+    }
+
+    private static int pinOrPasswordQualityToCredentialType(int quality) {
+        if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
+            return CREDENTIAL_TYPE_PASSWORD;
+        }
+        if (LockPatternUtils.isQualityNumericPin(quality)) {
+            return CREDENTIAL_TYPE_PIN;
+        }
+        throw new IllegalArgumentException("Quality is neither Pin nor password: " + quality);
     }
 
     private boolean isUserSecure(int userId) {
-        synchronized (mSpManager) {
-            if (isSyntheticPasswordBasedCredentialLocked(userId)) {
-                final long handle = getSyntheticPasswordHandleLocked(userId);
-                return mSpManager.getCredentialType(handle, userId) != CREDENTIAL_TYPE_NONE;
-            }
-        }
-        return mStorage.hasCredential(userId);
+        return getCredentialTypeInternal(userId) != CREDENTIAL_TYPE_NONE;
     }
 
     private void setKeystorePassword(byte[] password, int userHandle) {
@@ -1205,8 +1230,8 @@
         ks.unlock(userHandle, passwordString);
     }
 
-    @VisibleForTesting
-    protected byte[] getDecryptedPasswordForTiedProfile(int userId)
+    @VisibleForTesting /** Note: this method is overridden in unit tests */
+    protected LockscreenCredential getDecryptedPasswordForTiedProfile(int userId)
             throws KeyStoreException, UnrecoverableKeyException,
             NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
             InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
@@ -1230,7 +1255,10 @@
 
         cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
         decryptionResult = cipher.doFinal(encryptedPassword);
-        return decryptionResult;
+        LockscreenCredential credential = LockscreenCredential.createManagedPassword(
+                decryptionResult);
+        Arrays.fill(decryptionResult, (byte) 0);
+        return credential;
     }
 
     private void unlockChildProfile(int profileHandle, boolean ignoreUserNotAuthenticated,
@@ -1238,7 +1266,6 @@
             @Nullable ArrayList<PendingResetLockout> resetLockouts) {
         try {
             doVerifyCredential(getDecryptedPasswordForTiedProfile(profileHandle),
-                    CREDENTIAL_TYPE_PASSWORD,
                     challengeType, challenge, profileHandle, null /* progressCallback */,
                     resetLockouts);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
@@ -1276,17 +1303,17 @@
         final IProgressListener listener = new IProgressListener.Stub() {
             @Override
             public void onStarted(int id, Bundle extras) throws RemoteException {
-                Log.d(TAG, "unlockUser started");
+                Slog.d(TAG, "unlockUser started");
             }
 
             @Override
             public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
-                Log.d(TAG, "unlockUser progress " + progress);
+                Slog.d(TAG, "unlockUser progress " + progress);
             }
 
             @Override
             public void onFinished(int id, Bundle extras) throws RemoteException {
-                Log.d(TAG, "unlockUser finished");
+                Slog.d(TAG, "unlockUser finished");
                 latch.countDown();
             }
         };
@@ -1351,11 +1378,11 @@
                 && mUserManager.isUserRunning(userInfo.id);
     }
 
-    private Map<Integer, byte[]> getDecryptedPasswordsForAllTiedProfiles(int userId) {
+    private Map<Integer, LockscreenCredential> getDecryptedPasswordsForAllTiedProfiles(int userId) {
         if (mUserManager.getUserInfo(userId).isManagedProfile()) {
             return null;
         }
-        Map<Integer, byte[]> result = new ArrayMap<Integer, byte[]>();
+        Map<Integer, LockscreenCredential> result = new ArrayMap<>();
         final List<UserInfo> profiles = mUserManager.getProfiles(userId);
         final int size = profiles.size();
         for (int i = 0; i < size; i++) {
@@ -1393,7 +1420,7 @@
      * terminates when the user is a managed profile.
      */
     private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
-            Map<Integer, byte[]> profilePasswordMap) {
+            Map<Integer, LockscreenCredential> profilePasswordMap) {
         if (mUserManager.getUserInfo(userId).isManagedProfile()) {
             return;
         }
@@ -1408,20 +1435,23 @@
                     continue;
                 }
                 if (isSecure) {
-                    tieManagedProfileLockIfNecessary(managedUserId, null);
+                    tieManagedProfileLockIfNecessary(managedUserId,
+                            LockscreenCredential.createNone());
                 } else {
                     // We use cached work profile password computed before clearing the parent's
                     // credential, otherwise they get lost
-                    if (profilePasswordMap != null && profilePasswordMap.containsKey(managedUserId)) {
-                        setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE,
+                    if (profilePasswordMap != null
+                            && profilePasswordMap.containsKey(managedUserId)) {
+                        setLockCredentialInternal(LockscreenCredential.createNone(),
                                 profilePasswordMap.get(managedUserId),
-                                DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId,
+                                managedUserId,
                                 false, /* isLockTiedToParent= */ true);
                     } else {
                         Slog.wtf(TAG, "clear tied profile challenges, but no password supplied.");
-                        // Supplying null here would lead to untrusted credential change
-                        setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE, null,
-                                DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId,
+                        // Attempt an untrusted reset by supplying an empty credential.
+                        setLockCredentialInternal(LockscreenCredential.createNone(),
+                                LockscreenCredential.createNone(),
+                                managedUserId,
                                 true, /* isLockTiedToParent= */ true);
                     }
                     mStorage.removeChildProfileLock(managedUserId);
@@ -1445,8 +1475,7 @@
      * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} during an
      * unlock operation.
      */
-    private void sendCredentialsOnUnlockIfRequired(
-            int credentialType, @NonNull byte[] credential, int userId) {
+    private void sendCredentialsOnUnlockIfRequired(LockscreenCredential credential, int userId) {
         // Don't send credentials during the factory reset protection flow.
         if (userId == USER_FRP) {
             return;
@@ -1459,10 +1488,12 @@
             return;
         }
 
+        // RecoverableKeyStoreManager expects null for empty credential.
+        final byte[] secret = credential.isNone() ? null : credential.getCredential();
         // Send credentials for the user and any child profiles that share its lock screen.
         for (int profileId : getProfilesWithSameLockScreen(userId)) {
             mRecoverableKeyStoreManager.lockScreenSecretAvailable(
-                    credentialType, credential, profileId);
+                    credential.getType(), secret, profileId);
         }
     }
 
@@ -1471,7 +1502,7 @@
      * credentials are set/changed.
      */
     private void sendCredentialsOnChangeIfRequired(
-            int credentialType, byte[] credential, int userId, boolean isLockTiedToParent) {
+            LockscreenCredential credential, int userId, boolean isLockTiedToParent) {
         // A profile whose lock screen is being tied to its parent's will either have a randomly
         // generated credential (creation) or null (removal). We rely on the parent to send its
         // credentials for the profile in both cases as it stores the unified lock credential.
@@ -1479,10 +1510,12 @@
             return;
         }
 
+        // RecoverableKeyStoreManager expects null for empty credential.
+        final byte[] secret = credential.isNone() ? null : credential.getCredential();
         // Send credentials for the user and any child profiles that share its lock screen.
         for (int profileId : getProfilesWithSameLockScreen(userId)) {
             mRecoverableKeyStoreManager.lockScreenSecretChanged(
-                    credentialType, credential, profileId);
+                    credential.getType(), secret, profileId);
         }
     }
 
@@ -1505,9 +1538,8 @@
     // This method should be called by LockPatternUtil only, all internal methods in this class
     // should call setLockCredentialInternal.
     @Override
-    public boolean setLockCredential(byte[] credential, int type,
-            byte[] savedCredential, int requestedQuality, int userId,
-            boolean allowUntrustedChange) {
+    public boolean setLockCredential(LockscreenCredential credential,
+            LockscreenCredential savedCredential, int userId, boolean allowUntrustedChange) {
 
         if (!mLockPatternUtils.hasSecureLockScreen()) {
             throw new UnsupportedOperationException(
@@ -1515,11 +1547,11 @@
         }
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
-            if (!setLockCredentialInternal(credential, type, savedCredential, requestedQuality,
+            if (!setLockCredentialInternal(credential, savedCredential,
                     userId, allowUntrustedChange, /* isLockTiedToParent= */ false)) {
                 return false;
             }
-            setSeparateProfileChallengeEnabledLocked(userId, true, null);
+            setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
             notifyPasswordChanged(userId);
         }
         if (mUserManager.getUserInfo(userId).isManagedProfile()) {
@@ -1531,51 +1563,44 @@
     }
 
     /**
+     * @param savedCredential if the user is a managed profile with unified challenge and
+     *   savedCredential is empty, LSS will try to re-derive the profile password internally.
+     *     TODO (b/80170828): Fix this so profile password is always passed in.
      * @param isLockTiedToParent is {@code true} if {@code userId} is a profile and its new
      *     credentials are being tied to its parent's credentials.
      */
-    private boolean setLockCredentialInternal(byte[] credential, @CredentialType int credentialType,
-            byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange,
-            boolean isLockTiedToParent) {
-        // Normalize savedCredential and credential such that empty string is always represented
-        // as null.
-        if (savedCredential == null || savedCredential.length == 0) {
-            savedCredential = null;
-        }
-        if (credential == null || credential.length == 0) {
-            credential = null;
-        }
+    private boolean setLockCredentialInternal(LockscreenCredential credential,
+            LockscreenCredential savedCredential, int userId,
+            boolean allowUntrustedChange, boolean isLockTiedToParent) {
+        Preconditions.checkNotNull(credential);
+        Preconditions.checkNotNull(savedCredential);
         synchronized (mSpManager) {
             if (isSyntheticPasswordBasedCredentialLocked(userId)) {
-                return spBasedSetLockCredentialInternalLocked(credential, credentialType,
-                        savedCredential, requestedQuality, userId, allowUntrustedChange,
-                        isLockTiedToParent);
+                return spBasedSetLockCredentialInternalLocked(credential, savedCredential, userId,
+                        allowUntrustedChange, isLockTiedToParent);
             }
         }
 
-        if (credentialType == CREDENTIAL_TYPE_NONE) {
-            if (credential != null) {
-                Slog.wtf(TAG, "CredentialType is none, but credential is non-null.");
-            }
+        if (credential.isNone()) {
             clearUserKeyProtection(userId);
             gateKeeperClearSecureUserId(userId);
             mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId);
+            // Still update PASSWORD_TYPE_KEY if we are running in pre-synthetic password code path,
+            // since it forms part of the state that determines the credential type
+            // @see getCredentialTypeInternal
+            setKeyguardStoredQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
             setKeystorePassword(null, userId);
             fixateNewestUserKeyAuth(userId);
             synchronizeUnifiedWorkChallengeForProfiles(userId, null);
-            setUserPasswordMetrics(CREDENTIAL_TYPE_NONE, null, userId);
-            sendCredentialsOnChangeIfRequired(
-                    credentialType, credential, userId, isLockTiedToParent);
+            setUserPasswordMetrics(LockscreenCredential.createNone(), userId);
+            sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
             return true;
         }
-        if (credential == null) {
-            throw new IllegalArgumentException("Null credential with mismatched credential type");
-        }
 
         CredentialHash currentHandle = mStorage.readCredentialHash(userId);
         if (isManagedProfileWithUnifiedLock(userId)) {
             // get credential from keystore when managed profile has unified lock
-            if (savedCredential == null) {
+            if (savedCredential.isNone()) {
                 try {
                     savedCredential = getDecryptedPasswordForTiedProfile(userId);
                 } catch (FileNotFoundException e) {
@@ -1589,47 +1614,50 @@
             }
         } else {
             if (currentHandle.hash == null) {
-                if (savedCredential != null) {
+                if (!savedCredential.isNone()) {
                     Slog.w(TAG, "Saved credential provided, but none stored");
                 }
-                savedCredential = null;
+                savedCredential.close();
+                savedCredential = LockscreenCredential.createNone();
             }
         }
         synchronized (mSpManager) {
             if (shouldMigrateToSyntheticPasswordLocked(userId)) {
-                initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential,
-                        currentHandle.type, requestedQuality, userId);
-                return spBasedSetLockCredentialInternalLocked(credential, credentialType,
-                        savedCredential, requestedQuality, userId, allowUntrustedChange,
-                        isLockTiedToParent);
+                initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential, userId);
+                return spBasedSetLockCredentialInternalLocked(credential, savedCredential, userId,
+                        allowUntrustedChange, isLockTiedToParent);
             }
         }
         if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
-        byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential,
-                userId);
+        byte[] enrolledHandle = enrollCredential(currentHandle.hash,
+                savedCredential.getCredential(), credential.getCredential(), userId);
         if (enrolledHandle == null) {
             Slog.w(TAG, String.format("Failed to enroll %s: incorrect credential",
-                    credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern"));
+                    credential.isPattern() ? "pattern" : "password"));
             return false;
         }
-        CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
+        CredentialHash willStore = CredentialHash.create(enrolledHandle, credential.getType());
         mStorage.writeCredentialHash(willStore, userId);
+        // Still update PASSWORD_TYPE_KEY if we are running in pre-synthetic password code path,
+        // since it forms part of the state that determines the credential type
+        // @see getCredentialTypeInternal
+        setKeyguardStoredQuality(
+                LockPatternUtils.credentialTypeToPasswordQuality(credential.getType()), userId);
         // push new secret and auth token to vold
         GateKeeperResponse gkResponse;
         try {
             gkResponse = getGateKeeperService().verifyChallenge(userId, 0, willStore.hash,
-                    credential);
+                    credential.getCredential());
         } catch (RemoteException e) {
             throw new IllegalStateException("Failed to verify current credential", e);
         }
         setUserKeyProtection(userId, credential, convertResponse(gkResponse));
         fixateNewestUserKeyAuth(userId);
         // Refresh the auth token
-        doVerifyCredential(credential, credentialType, CHALLENGE_FROM_CALLER, 0, userId,
+        doVerifyCredential(credential, CHALLENGE_FROM_CALLER, 0, userId,
                 null /* progressCallback */);
         synchronizeUnifiedWorkChallengeForProfiles(userId, null);
-        sendCredentialsOnChangeIfRequired(
-                credentialType, credential, userId, isLockTiedToParent);
+        sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
         return true;
     }
 
@@ -1637,8 +1665,8 @@
         return VerifyCredentialResponse.fromGateKeeperResponse(gateKeeperResponse);
     }
 
-    @VisibleForTesting
-    protected void tieProfileLockToParent(int userId, byte[] password) {
+    @VisibleForTesting /** Note: this method is overridden in unit tests */
+    protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
         if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
         byte[] encryptionResult;
         byte[] iv;
@@ -1673,7 +1701,7 @@
                         KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
                                 + KeyProperties.ENCRYPTION_PADDING_NONE);
                 cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey);
-                encryptionResult = cipher.doFinal(password);
+                encryptionResult = cipher.doFinal(password.getCredential());
                 iv = cipher.getIV();
             } finally {
                 // The original key can now be discarded.
@@ -1728,7 +1756,8 @@
         addUserKeyAuth(userId, null, key);
     }
 
-    private void setUserKeyProtection(int userId, byte[] credential, VerifyCredentialResponse vcr) {
+    private void setUserKeyProtection(int userId, LockscreenCredential credential,
+            VerifyCredentialResponse vcr) {
         if (DEBUG) Slog.d(TAG, "setUserKeyProtection: user=" + userId);
         if (vcr == null) {
             throw new IllegalArgumentException("Null response verifying a credential we just set");
@@ -1749,7 +1778,7 @@
         addUserKeyAuth(userId, null, null);
     }
 
-    private static byte[] secretFromCredential(byte[] credential) {
+    private static byte[] secretFromCredential(LockscreenCredential credential) {
         try {
             MessageDigest digest = MessageDigest.getInstance("SHA-512");
             // Personalize the hash
@@ -1757,7 +1786,7 @@
             // Pad it to the block size of the hash function
             personalization = Arrays.copyOf(personalization, 128);
             digest.update(personalization);
-            digest.update(credential);
+            digest.update(credential.getCredential());
             return digest.digest();
         } catch (NoSuchAlgorithmException e) {
             throw new IllegalStateException("NoSuchAlgorithmException for SHA-512");
@@ -1768,7 +1797,7 @@
         try {
             return mStorageManager.isUserKeyUnlocked(userId);
         } catch (RemoteException e) {
-            Log.e(TAG, "failed to check user key locked state", e);
+            Slog.e(TAG, "failed to check user key locked state", e);
             return false;
         }
     }
@@ -1815,7 +1844,7 @@
         checkWritePermission(userId);
         if (DEBUG) Slog.v(TAG, "Reset keystore for user: " + userId);
         int managedUserId = -1;
-        byte[] managedUserDecryptedPassword = null;
+        LockscreenCredential managedUserDecryptedPassword = null;
         final List<UserInfo> profiles = mUserManager.getProfiles(userId);
         for (UserInfo pi : profiles) {
             // Unlock managed profile with unified lock
@@ -1852,30 +1881,30 @@
                 tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
             }
         }
-        if (managedUserDecryptedPassword != null && managedUserDecryptedPassword.length > 0) {
-            Arrays.fill(managedUserDecryptedPassword, (byte) 0);
+        if (managedUserDecryptedPassword != null) {
+            managedUserDecryptedPassword.zeroize();
         }
     }
 
     @Override
-    public VerifyCredentialResponse checkCredential(byte[] credential, int type, int userId,
+    public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId,
             ICheckCredentialProgressCallback progressCallback) {
         checkPasswordReadPermission(userId);
-        return doVerifyCredential(credential, type, CHALLENGE_NONE, 0, userId, progressCallback);
+        return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback);
     }
 
     @Override
-    public VerifyCredentialResponse verifyCredential(byte[] credential, int type, long challenge,
-            int userId) {
+    public VerifyCredentialResponse verifyCredential(LockscreenCredential credential,
+            long challenge, int userId) {
         checkPasswordReadPermission(userId);
-        return doVerifyCredential(credential, type, CHALLENGE_FROM_CALLER, challenge, userId,
+        return doVerifyCredential(credential, CHALLENGE_FROM_CALLER, challenge, userId,
                 null /* progressCallback */);
     }
 
-    private VerifyCredentialResponse doVerifyCredential(byte[] credential, int credentialType,
+    private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
             @ChallengeType int challengeType, long challenge, int userId,
             ICheckCredentialProgressCallback progressCallback) {
-        return doVerifyCredential(credential, credentialType, challengeType, challenge, userId,
+        return doVerifyCredential(credential, challengeType, challenge, userId,
                 progressCallback, null /* resetLockouts */);
     }
 
@@ -1883,25 +1912,25 @@
      * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
      * format.
      */
-    private VerifyCredentialResponse doVerifyCredential(byte[] credential, int credentialType,
+    private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
             @ChallengeType int challengeType, long challenge, int userId,
             ICheckCredentialProgressCallback progressCallback,
             @Nullable ArrayList<PendingResetLockout> resetLockouts) {
-        if (credential == null || credential.length == 0) {
+        if (credential == null || credential.isNone()) {
             throw new IllegalArgumentException("Credential can't be null or empty");
         }
-        if (userId == USER_FRP && Settings.Global.getInt(mContext.getContentResolver(),
+        if (userId == USER_FRP && mInjector.settingsGlobalGetInt(mContext.getContentResolver(),
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
             Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
             return VerifyCredentialResponse.ERROR;
         }
         VerifyCredentialResponse response = null;
-        response = spBasedDoVerifyCredential(credential, credentialType, challengeType, challenge,
+        response = spBasedDoVerifyCredential(credential, challengeType, challenge,
                 userId, progressCallback, resetLockouts);
         // The user employs synthetic password based credential.
         if (response != null) {
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-                sendCredentialsOnUnlockIfRequired(credentialType, credential, userId);
+                sendCredentialsOnUnlockIfRequired(credential, userId);
             }
             return response;
         }
@@ -1912,9 +1941,9 @@
         }
 
         final CredentialHash storedHash = mStorage.readCredentialHash(userId);
-        if (storedHash.type != credentialType) {
+        if (!credential.checkAgainstStoredType(storedHash.type)) {
             Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
-                    + " stored: " + storedHash.type + " passed in: " + credentialType);
+                    + " stored: " + storedHash.type + " passed in: " + credential.getType());
             return VerifyCredentialResponse.ERROR;
         }
 
@@ -1929,7 +1958,7 @@
     }
 
     @Override
-    public VerifyCredentialResponse verifyTiedProfileChallenge(byte[] credential, int type,
+    public VerifyCredentialResponse verifyTiedProfileChallenge(LockscreenCredential credential,
             long challenge, int userId) {
         checkPasswordReadPermission(userId);
         if (!isManagedProfileWithUnifiedLock(userId)) {
@@ -1939,7 +1968,6 @@
         // Unlock parent by using parent's challenge
         final VerifyCredentialResponse parentResponse = doVerifyCredential(
                 credential,
-                type,
                 CHALLENGE_FROM_CALLER,
                 challenge,
                 parentProfileId,
@@ -1952,7 +1980,6 @@
         try {
             // Unlock work profile, and work profile with unified lock must use password only
             return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
-                    CREDENTIAL_TYPE_PASSWORD,
                     CHALLENGE_FROM_CALLER,
                     challenge,
                     userId, null /* progressCallback */);
@@ -1971,15 +1998,14 @@
      * hash to GK.
      */
     private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
-            byte[] credential, @ChallengeType int challengeType, long challenge,
+            LockscreenCredential credential, @ChallengeType int challengeType, long challenge,
             ICheckCredentialProgressCallback progressCallback) {
-        if ((storedHash == null || storedHash.hash.length == 0)
-                    && (credential == null || credential.length == 0)) {
+        if ((storedHash == null || storedHash.hash.length == 0) && credential.isNone()) {
             // don't need to pass empty credentials to GateKeeper
             return VerifyCredentialResponse.OK;
         }
 
-        if (storedHash == null || credential == null || credential.length == 0) {
+        if (storedHash == null || storedHash.hash.length == 0 || credential.isNone()) {
             return VerifyCredentialResponse.ERROR;
         }
 
@@ -1989,8 +2015,8 @@
 
         GateKeeperResponse gateKeeperResponse;
         try {
-            gateKeeperResponse = getGateKeeperService()
-                    .verifyChallenge(userId, challenge, storedHash.hash, credential);
+            gateKeeperResponse = getGateKeeperService().verifyChallenge(
+                    userId, challenge, storedHash.hash, credential.getCredential());
         } catch (RemoteException e) {
             Slog.e(TAG, "gatekeeper verify failed", e);
             gateKeeperResponse = GateKeeperResponse.ERROR;
@@ -2006,11 +2032,11 @@
                 try {
                     progressCallback.onCredentialVerified();
                 } catch (RemoteException e) {
-                    Log.w(TAG, "progressCallback throws exception", e);
+                    Slog.w(TAG, "progressCallback throws exception", e);
                 }
             }
-            setUserPasswordMetrics(storedHash.type, credential, userId);
-            unlockKeystore(credential, userId);
+            setUserPasswordMetrics(credential, userId);
+            unlockKeystore(credential.getCredential(), userId);
 
             Slog.i(TAG, "Unlocking user " + userId + " with token length "
                     + response.getPayload().length);
@@ -2019,27 +2045,22 @@
             if (isManagedProfileWithSeparatedLock(userId)) {
                 setDeviceUnlockedForUser(userId);
             }
-            int reEnrollQuality = storedHash.type == CREDENTIAL_TYPE_PATTERN
-                    ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
-                    : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
-                    /* TODO(roosa): keep the same password quality */;
             if (shouldReEnroll) {
-                setLockCredentialInternal(credential, storedHash.type, credential,
-                        reEnrollQuality, userId, false, /* isLockTiedToParent= */ false);
+                setLockCredentialInternal(credential, credential,
+                        userId, false, /* isLockTiedToParent= */ false);
             } else {
                 // Now that we've cleared of all required GK migration, let's do the final
                 // migration to synthetic password.
                 synchronized (mSpManager) {
                     if (shouldMigrateToSyntheticPasswordLocked(userId)) {
                         AuthenticationToken auth = initializeSyntheticPasswordLocked(
-                                storedHash.hash, credential, storedHash.type, reEnrollQuality,
-                                userId);
+                                storedHash.hash, credential,  userId);
                         activateEscrowTokens(auth, userId);
                     }
                 }
             }
             // Use credentials to create recoverable keystore snapshot.
-            sendCredentialsOnUnlockIfRequired(storedHash.type, credential, userId);
+            sendCredentialsOnUnlockIfRequired(credential, userId);
 
         } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
             if (response.getTimeout() > 0) {
@@ -2055,12 +2076,9 @@
      * when the user is authenticating or when a new password is being set. In comparison,
      * {@link #notifyPasswordChanged} only needs to be called when the user changes the password.
      */
-    private void setUserPasswordMetrics(@CredentialType int credentialType, byte[] password,
-            @UserIdInt int userHandle) {
+    private void setUserPasswordMetrics(LockscreenCredential password, @UserIdInt int userHandle) {
         synchronized (this) {
-            mUserPasswordMetrics.put(userHandle,
-                    PasswordMetrics.computeForCredential(
-                            LockscreenCredential.createRaw(credentialType, password)));
+            mUserPasswordMetrics.put(userHandle, PasswordMetrics.computeForCredential(password));
         }
     }
 
@@ -2089,6 +2107,14 @@
         });
     }
 
+    private LockscreenCredential createPattern(String patternString) {
+        final byte[] patternBytes = patternString.getBytes();
+        LockscreenCredential pattern = LockscreenCredential.createPattern(
+                LockPatternUtils.byteArrayToPattern(patternBytes));
+        Arrays.fill(patternBytes, (byte) 0);
+        return pattern;
+    }
+
     @Override
     public boolean checkVoldPassword(int userId) {
         if (!mFirstCallToVold) {
@@ -2119,30 +2145,34 @@
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        if (password == null) {
+        if (TextUtils.isEmpty(password)) {
             return false;
         }
 
         try {
-            if (mLockPatternUtils.isLockPatternEnabled(userId)) {
-                if (checkCredential(password.getBytes(), CREDENTIAL_TYPE_PATTERN,
-                        userId, null /* progressCallback */)
-                                .getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
-                    return true;
-                }
+            final LockscreenCredential credential;
+            switch (getCredentialTypeInternal(userId)) {
+                case CREDENTIAL_TYPE_PATTERN:
+                    credential = createPattern(password);
+                    break;
+                case CREDENTIAL_TYPE_PIN:
+                    credential = LockscreenCredential.createPin(password);
+                    break;
+                case CREDENTIAL_TYPE_PASSWORD:
+                    credential = LockscreenCredential.createPassword(password);
+                    break;
+                default:
+                    credential = null;
+                    Slog.e(TAG, "Unknown credential type");
             }
-        } catch (Exception e) {
-        }
 
-        try {
-            if (mLockPatternUtils.isLockPasswordEnabled(userId)) {
-                if (checkCredential(password.getBytes(), CREDENTIAL_TYPE_PASSWORD,
-                        userId, null /* progressCallback */)
+            if (credential != null
+                    && checkCredential(credential, userId, null /* progressCallback */)
                                 .getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
-                    return true;
-                }
+                return true;
             }
         } catch (Exception e) {
+            Slog.e(TAG, "checkVoldPassword failed: ", e);
         }
 
         return false;
@@ -2524,7 +2554,7 @@
     @GuardedBy("mSpManager")
     @VisibleForTesting
     protected AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
-            byte[] credential, int credentialType, int requestedQuality, int userId) {
+            LockscreenCredential credential, int userId) {
         Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
         final AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(
                 getGateKeeperService(), credentialHash, credential, userId);
@@ -2534,8 +2564,8 @@
             return null;
         }
         long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
-                credential, credentialType, auth, requestedQuality, userId);
-        if (credential != null) {
+                credential, auth, userId);
+        if (!credential.isNone()) {
             if (credentialHash == null) {
                 // Since when initializing SP, we didn't provide an existing password handle
                 // for it to migrate SID, we need to create a new SID for the user.
@@ -2567,6 +2597,13 @@
 
     }
 
+    @VisibleForTesting
+    boolean isSyntheticPasswordBasedCredential(int userId) {
+        synchronized (mSpManager) {
+            return isSyntheticPasswordBasedCredentialLocked(userId);
+        }
+    }
+
     private boolean isSyntheticPasswordBasedCredentialLocked(int userId) {
         if (userId == USER_FRP) {
             final int type = mStorage.readPersistentDataBlock().type;
@@ -2592,8 +2629,8 @@
         setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
     }
 
-    private VerifyCredentialResponse spBasedDoVerifyCredential(byte[] userCredential,
-            @CredentialType int credentialType, @ChallengeType int challengeType, long challenge,
+    private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
+            @ChallengeType int challengeType, long challenge,
             int userId, ICheckCredentialProgressCallback progressCallback,
             @Nullable ArrayList<PendingResetLockout> resetLockouts) {
 
@@ -2601,9 +2638,6 @@
 
         Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId + " challengeType=" + challengeType
                 + " hasEnrolledBiometrics=" + hasEnrolledBiometrics);
-        if (credentialType == CREDENTIAL_TYPE_NONE) {
-            userCredential = null;
-        }
 
         final PackageManager pm = mContext.getPackageManager();
         // TODO: When lockout is handled under the HAL for all biometrics (fingerprint),
@@ -2625,17 +2659,13 @@
             }
             if (userId == USER_FRP) {
                 return mSpManager.verifyFrpCredential(getGateKeeperService(),
-                        userCredential, credentialType, progressCallback);
+                        userCredential, progressCallback);
             }
 
             long handle = getSyntheticPasswordHandleLocked(userId);
             authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
                     getGateKeeperService(), handle, userCredential, userId, progressCallback);
 
-            if (authResult.credentialType != credentialType) {
-                Slog.e(TAG, "Credential type mismatch.");
-                return VerifyCredentialResponse.ERROR;
-            }
             response = authResult.gkResponse;
             // credential has matched
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -2653,7 +2683,7 @@
         }
 
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-            setUserPasswordMetrics(credentialType, userCredential, userId);
+            setUserPasswordMetrics(userCredential, userId);
             unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
 
             // Do resetLockout / revokeChallenge when all profiles are unlocked
@@ -2700,14 +2730,13 @@
      * added back when new password is set in future.
      */
     @GuardedBy("mSpManager")
-    private long setLockCredentialWithAuthTokenLocked(byte[] credential,
-            @CredentialType int credentialType, AuthenticationToken auth, int requestedQuality,
-            int userId) {
+    private long setLockCredentialWithAuthTokenLocked(LockscreenCredential credential,
+            AuthenticationToken auth, int userId) {
         if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
         long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
-                credential, credentialType, auth, requestedQuality, userId);
-        final Map<Integer, byte[]> profilePasswords;
-        if (credential != null) {
+                credential, auth, userId);
+        final Map<Integer, LockscreenCredential> profilePasswords;
+        if (!credential.isNone()) {
             // not needed by synchronizeUnifiedWorkChallengeForProfiles()
             profilePasswords = null;
 
@@ -2748,11 +2777,11 @@
         setSyntheticPasswordHandleLocked(newHandle, userId);
         synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
 
-        setUserPasswordMetrics(credentialType, credential, userId);
+        setUserPasswordMetrics(credential, userId);
 
         if (profilePasswords != null) {
-            for (Map.Entry<Integer, byte[]> entry : profilePasswords.entrySet()) {
-                Arrays.fill(entry.getValue(), (byte) 0);
+            for (Map.Entry<Integer, LockscreenCredential> entry : profilePasswords.entrySet()) {
+                entry.getValue().zeroize();
             }
         }
 
@@ -2838,12 +2867,17 @@
         };
     }
 
+    /**
+     * @param savedCredential if the user is a managed profile with unified challenge and
+     *   savedCredential is empty, LSS will try to re-derive the profile password internally.
+     *     TODO (b/80170828): Fix this so profile password is always passed in.
+     */
     @GuardedBy("mSpManager")
-    private boolean spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType,
-            byte[] savedCredential, int requestedQuality, int userId,
+    private boolean spBasedSetLockCredentialInternalLocked(LockscreenCredential credential,
+            LockscreenCredential savedCredential, int userId,
             boolean allowUntrustedChange, boolean isLockTiedToParent) {
         if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
-        if (isManagedProfileWithUnifiedLock(userId)) {
+        if (savedCredential.isNone() && isManagedProfileWithUnifiedLock(userId)) {
             // get credential from keystore when managed profile has unified lock
             try {
                 savedCredential = getDecryptedPasswordForTiedProfile(userId);
@@ -2863,9 +2897,8 @@
         AuthenticationToken auth = authResult.authToken;
 
         // If existing credential is provided, the existing credential must match.
-        if (savedCredential != null && auth == null) {
-            Slog.w(TAG, String.format("Failed to enroll %s: incorrect credential",
-                    credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern"));
+        if (!savedCredential.isNone() && auth == null) {
+            Slog.w(TAG, "Failed to enroll: incorrect credential");
             return false;
         }
         boolean untrustedReset = false;
@@ -2898,8 +2931,7 @@
                 // setLockCredentialWithAuthTokenLocked next
                 mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
             }
-            setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, requestedQuality,
-                    userId);
+            setLockCredentialWithAuthTokenLocked(credential, auth, userId);
             mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
         } else {
             throw new IllegalStateException(
@@ -2908,7 +2940,7 @@
             // requestedQuality, userId) instead if we still allow untrusted reset that changes
             // synthetic password. That would invalidate existing escrow tokens though.
         }
-        sendCredentialsOnChangeIfRequired(credentialType, credential, userId, isLockTiedToParent);
+        sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
         return true;
     }
 
@@ -2919,11 +2951,8 @@
      * If user is a managed profile with unified challenge, currentCredential is ignored.
      */
     @Override
-    public byte[] getHashFactor(byte[] currentCredential, int userId) {
+    public byte[] getHashFactor(LockscreenCredential currentCredential, int userId) {
         checkPasswordReadPermission(userId);
-        if (currentCredential == null || currentCredential.length == 0) {
-            currentCredential = null;
-        }
         if (isManagedProfileWithUnifiedLock(userId)) {
             try {
                 currentCredential = getDecryptedPasswordForTiedProfile(userId);
@@ -2957,13 +2986,12 @@
             AuthenticationToken auth = null;
             if (!isUserSecure(userId)) {
                 if (shouldMigrateToSyntheticPasswordLocked(userId)) {
-                    auth = initializeSyntheticPasswordLocked(null, null,
-                            CREDENTIAL_TYPE_NONE,
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
+                    auth = initializeSyntheticPasswordLocked(
+                            /* credentialHash */ null, LockscreenCredential.createNone(), userId);
                 } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
                     long pwdHandle = getSyntheticPasswordHandleLocked(userId);
                     auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
-                            pwdHandle, null, userId, null).authToken;
+                            pwdHandle, LockscreenCredential.createNone(), userId, null).authToken;
                 }
             }
             if (isSyntheticPasswordBasedCredentialLocked(userId)) {
@@ -3023,21 +3051,21 @@
         }
     }
 
-    private boolean setLockCredentialWithToken(byte[] credential, int type, long tokenHandle,
-            byte[] token, int requestedQuality, int userId) {
+    private boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
+            byte[] token, int userId) {
         boolean result;
         synchronized (mSpManager) {
             if (!mSpManager.hasEscrowData(userId)) {
                 throw new SecurityException("Escrow token is disabled on the current user");
             }
-            result = setLockCredentialWithTokenInternalLocked(credential, type, tokenHandle, token,
-                    requestedQuality, userId);
+            result = setLockCredentialWithTokenInternalLocked(
+                    credential, tokenHandle, token, userId);
         }
         if (result) {
             synchronized (mSeparateChallengeLock) {
-                setSeparateProfileChallengeEnabledLocked(userId, true, null);
+                setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
             }
-            if (credential == null) {
+            if (credential.isNone()) {
                 // If clearing credential, unlock the user manually in order to progress user start
                 // Call unlockUser() on a handler thread so no lock is held (either by LSS or by
                 // the caller like DPMS), otherwise it can lead to deadlock.
@@ -3050,8 +3078,8 @@
     }
 
     @GuardedBy("mSpManager")
-    private boolean setLockCredentialWithTokenInternalLocked(byte[] credential, int type,
-            long tokenHandle, byte[] token, int requestedQuality, int userId) {
+    private boolean setLockCredentialWithTokenInternalLocked(LockscreenCredential credential,
+            long tokenHandle, byte[] token, int userId) {
         final AuthenticationResult result;
         result = mSpManager.unwrapTokenBasedSyntheticPassword(
                 getGateKeeperService(), tokenHandle, token, userId);
@@ -3066,11 +3094,8 @@
                     + "verification.");
             return false;
         }
-        // TODO: refactor usage of PASSWORD_TYPE_KEY b/65239740
-        setLong(LockPatternUtils.PASSWORD_TYPE_KEY, requestedQuality, userId);
         long oldHandle = getSyntheticPasswordHandleLocked(userId);
-        setLockCredentialWithAuthTokenLocked(credential, type, result.authToken,
-                requestedQuality, userId);
+        setLockCredentialWithAuthTokenLocked(credential, result.authToken, userId);
         mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId);
 
         onAuthTokenKnownForUser(userId, result.authToken);
@@ -3132,11 +3157,10 @@
             }
             // It's OK to dump the password type since anyone with physical access can just
             // observe it from the keyguard directly.
-            pw.println("PasswordType: " + getLong(LockPatternUtils.PASSWORD_TYPE_KEY, 0, userId));
-            pw.println("hasPassword: " + havePassword(userId));
-            pw.println("hasPattern: " + havePattern(userId)); // print raw credential type instead?
+            pw.println("Quality: " + getKeyguardStoredQuality(userId));
+            pw.println("CredentialType: " + getCredentialTypeInternal(userId));
             pw.println("SeparateChallenge: " + getSeparateProfileChallengeEnabled(userId));
-            pw.println(String.format("metrics: %s",
+            pw.println(String.format("Metrics: %s",
                     getUserPasswordMetrics(userId) != null ? "known" : "unknown"));
             pw.decreaseIndent();
         }
@@ -3298,14 +3322,14 @@
         }
 
         @Override
-        public boolean setLockCredentialWithToken(byte[] credential, int type,
-                long tokenHandle, byte[] token, int requestedQuality, int userId) {
+        public boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
+                byte[] token, int userId) {
             if (!mLockPatternUtils.hasSecureLockScreen()) {
                 throw new UnsupportedOperationException(
                         "This operation requires secure lock screen feature.");
             }
-            return LockSettingsService.this.setLockCredentialWithToken(credential, type,
-                    tokenHandle, token, requestedQuality, userId);
+            return LockSettingsService.this.setLockCredentialWithToken(
+                    credential, tokenHandle, token, userId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 0a8e5bd..0f8561e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -16,14 +16,22 @@
 
 package com.android.server.locksettings;
 
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
 import android.app.ActivityManager;
+import android.app.admin.PasswordMetrics;
 import android.os.ShellCommand;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
 import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
 
 import java.io.PrintWriter;
+import java.util.List;
 
 class LockSettingsShellCommand extends ShellCommand {
 
@@ -70,18 +78,19 @@
             if (!checkCredential()) {
                 return -1;
             }
+            boolean success = true;
             switch (cmd) {
                 case COMMAND_SET_PATTERN:
-                    runSetPattern();
+                    success = runSetPattern();
                     break;
                 case COMMAND_SET_PASSWORD:
-                    runSetPassword();
+                    success = runSetPassword();
                     break;
                 case COMMAND_SET_PIN:
-                    runSetPin();
+                    success = runSetPin();
                     break;
                 case COMMAND_CLEAR:
-                    runClear();
+                    success = runClear();
                     break;
                 case COMMAND_SP:
                     runChangeSp();
@@ -102,7 +111,7 @@
                     getErrPrintWriter().println("Unknown command: " + cmd);
                     break;
             }
-            return 0;
+            return success ? 0 : -1;
         } catch (Exception e) {
             getErrPrintWriter().println("Error while executing command: " + cmd);
             e.printStackTrace(getErrPrintWriter());
@@ -201,34 +210,66 @@
         }
     }
 
-    private void runSetPattern() {
-        mLockPatternUtils.setLockCredential(
-                LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
-                        mNew.getBytes())),
-                getOldCredential(),
-                mCurrentUserId);
+    private boolean runSetPattern() {
+        final LockscreenCredential pattern = LockscreenCredential.createPattern(
+                LockPatternUtils.byteArrayToPattern(mNew.getBytes()));
+        if (!isNewCredentialSufficient(pattern)) {
+            return false;
+        }
+        mLockPatternUtils.setLockCredential(pattern, getOldCredential(), mCurrentUserId);
         getOutPrintWriter().println("Pattern set to '" + mNew + "'");
+        return true;
     }
 
-    private void runSetPassword() {
-        mLockPatternUtils.setLockCredential(LockscreenCredential.createPassword(mNew),
-                getOldCredential(),
-                mCurrentUserId);
+    private boolean runSetPassword() {
+        final LockscreenCredential password = LockscreenCredential.createPassword(mNew);
+        if (!isNewCredentialSufficient(password)) {
+            return false;
+        }
+        mLockPatternUtils.setLockCredential(password, getOldCredential(), mCurrentUserId);
         getOutPrintWriter().println("Password set to '" + mNew + "'");
+        return true;
     }
 
-    private void runSetPin() {
-        mLockPatternUtils.setLockCredential(LockscreenCredential.createPin(mNew),
-                getOldCredential(),
-                mCurrentUserId);
+    private boolean runSetPin() {
+        final LockscreenCredential pin = LockscreenCredential.createPin(mNew);
+        if (!isNewCredentialSufficient(pin)) {
+            return false;
+        }
+        mLockPatternUtils.setLockCredential(pin, getOldCredential(), mCurrentUserId);
         getOutPrintWriter().println("Pin set to '" + mNew + "'");
+        return true;
     }
 
-    private void runClear() {
-        mLockPatternUtils.setLockCredential(LockscreenCredential.createNone(),
-                getOldCredential(),
-                mCurrentUserId);
+    private boolean runClear() {
+        LockscreenCredential none = LockscreenCredential.createNone();
+        if (!isNewCredentialSufficient(none)) {
+            return false;
+        }
+        mLockPatternUtils.setLockCredential(none, getOldCredential(), mCurrentUserId);
         getOutPrintWriter().println("Lock credential cleared");
+        return true;
+    }
+
+    private boolean isNewCredentialSufficient(LockscreenCredential credential) {
+        final PasswordMetrics requiredMetrics =
+                mLockPatternUtils.getRequestedPasswordMetrics(mCurrentUserId);
+        final List<PasswordValidationError> errors;
+        if (credential.isPassword() || credential.isPin()) {
+            errors = PasswordMetrics.validatePassword(requiredMetrics, PASSWORD_COMPLEXITY_NONE,
+                    credential.isPin(), credential.getCredential());
+        } else {
+            PasswordMetrics metrics = new PasswordMetrics(
+                    credential.isPattern() ? CREDENTIAL_TYPE_PATTERN : CREDENTIAL_TYPE_NONE);
+            errors = PasswordMetrics.validatePasswordMetrics(
+                    requiredMetrics, PASSWORD_COMPLEXITY_NONE, false /* isPin */, metrics);
+        }
+        if (!errors.isEmpty()) {
+            getOutPrintWriter().println(
+                    "New credential doesn't satisfy admin policies: " + errors.get(0));
+            return false;
+        }
+        return true;
     }
 
     private void runSetDisabled() {
@@ -243,9 +284,7 @@
     }
 
     private boolean checkCredential() {
-        final boolean havePassword = mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId);
-        final boolean havePattern = mLockPatternUtils.isLockPatternEnabled(mCurrentUserId);
-        if (havePassword || havePattern) {
+        if (mLockPatternUtils.isSecure(mCurrentUserId)) {
             if (mLockPatternUtils.isManagedProfileWithUnifiedChallenge(mCurrentUserId)) {
                 getOutPrintWriter().println("Profile uses unified challenge");
                 return false;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index f4cad63..3dab3ce 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -30,7 +30,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -214,7 +213,7 @@
     private CredentialHash readPasswordHashIfExists(int userId) {
         byte[] stored = readFile(getLockPasswordFilename(userId));
         if (!ArrayUtils.isEmpty(stored)) {
-            return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
+            return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN);
         }
         return null;
     }
@@ -270,10 +269,6 @@
         return hasFile(getLockPatternFilename(userId));
     }
 
-    public boolean hasCredential(int userId) {
-        return hasPassword(userId) || hasPattern(userId);
-    }
-
     private boolean hasFile(String name) {
         byte[] contents = readFile(name);
         return contents != null && contents.length > 0;
@@ -360,11 +355,15 @@
     public void writeCredentialHash(CredentialHash hash, int userId) {
         byte[] patternHash = null;
         byte[] passwordHash = null;
-
-        if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+        if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN
+                || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PIN) {
             passwordHash = hash.hash;
         } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
             patternHash = hash.hash;
+        } else {
+            Preconditions.checkArgument(hash.type == LockPatternUtils.CREDENTIAL_TYPE_NONE,
+                    "Unknown credential type");
         }
         writeFile(getLockPasswordFilename(userId), passwordHash);
         writeFile(getLockPatternFilename(userId), patternHash);
@@ -523,8 +522,8 @@
         mCache.clear();
     }
 
-    @Nullable
-    public PersistentDataBlockManagerInternal getPersistentDataBlock() {
+    @Nullable @VisibleForTesting
+    PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
         if (mPersistentDataBlockManagerInternal == null) {
             mPersistentDataBlockManagerInternal =
                     LocalServices.getService(PersistentDataBlockManagerInternal.class);
@@ -534,7 +533,7 @@
 
     public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi,
             byte[] payload) {
-        PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
+        PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager();
         if (persistentDataBlock == null) {
             return;
         }
@@ -543,7 +542,7 @@
     }
 
     public PersistentData readPersistentDataBlock() {
-        PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
+        PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager();
         if (persistentDataBlock == null) {
             return PersistentData.NONE;
         }
@@ -699,7 +698,7 @@
             }
 
             if (upgradeVersion != DATABASE_VERSION) {
-                Log.w(TAG, "Failed to upgrade database!");
+                Slog.w(TAG, "Failed to upgrade database!");
             }
         }
     }
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 955a9aa..53d922b 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.weaver.V1_0.IWeaver;
@@ -35,13 +34,13 @@
 import android.service.gatekeeper.GateKeeperResponse;
 import android.service.gatekeeper.IGateKeeperService;
 import android.util.ArrayMap;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
 
@@ -138,7 +137,6 @@
     static class AuthenticationResult {
         public AuthenticationToken authToken;
         public VerifyCredentialResponse gkResponse;
-        public int credentialType;
     }
 
     static class AuthenticationToken {
@@ -220,7 +218,7 @@
         byte scryptN;
         byte scryptR;
         byte scryptP;
-        public int passwordType;
+        public int credentialType;
         byte[] salt;
         // For GateKeeper-based credential, this is the password handle returned by GK,
         // for weaver-based credential, this is empty.
@@ -231,7 +229,7 @@
             result.scryptN = PASSWORD_SCRYPT_N;
             result.scryptR = PASSWORD_SCRYPT_R;
             result.scryptP = PASSWORD_SCRYPT_P;
-            result.passwordType = passwordType;
+            result.credentialType = passwordType;
             result.salt = secureRandom(PASSWORD_SALT_LENGTH);
             return result;
         }
@@ -241,7 +239,7 @@
             ByteBuffer buffer = ByteBuffer.allocate(data.length);
             buffer.put(data, 0, data.length);
             buffer.flip();
-            result.passwordType = buffer.getInt();
+            result.credentialType = buffer.getInt();
             result.scryptN = buffer.get();
             result.scryptR = buffer.get();
             result.scryptP = buffer.get();
@@ -263,7 +261,7 @@
             ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 3 * Byte.BYTES
                     + Integer.BYTES + salt.length + Integer.BYTES +
                     (passwordHandle != null ? passwordHandle.length : 0));
-            buffer.putInt(passwordType);
+            buffer.putInt(credentialType);
             buffer.put(scryptN);
             buffer.put(scryptR);
             buffer.put(scryptP);
@@ -366,11 +364,11 @@
         try {
             int writeStatus = mWeaver.write(slot, toByteArrayList(key), toByteArrayList(value));
             if (writeStatus != WeaverStatus.OK) {
-                Log.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus);
+                Slog.e(TAG, "weaver write failed, slot: " + slot + " status: " + writeStatus);
                 return null;
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "weaver write failed", e);
+            Slog.e(TAG, "weaver write failed", e);
             return null;
         }
         return value;
@@ -401,31 +399,31 @@
                             break;
                         case WeaverReadStatus.THROTTLE:
                             response[0] = new VerifyCredentialResponse(readResponse.timeout);
-                            Log.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
+                            Slog.e(TAG, "weaver read failed (THROTTLE), slot: " + slot);
                             break;
                         case WeaverReadStatus.INCORRECT_KEY:
                             if (readResponse.timeout == 0) {
                                 response[0] = VerifyCredentialResponse.ERROR;
-                                Log.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
+                                Slog.e(TAG, "weaver read failed (INCORRECT_KEY), slot: " + slot);
                             } else {
                                 response[0] = new VerifyCredentialResponse(readResponse.timeout);
-                                Log.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: "
+                                Slog.e(TAG, "weaver read failed (INCORRECT_KEY/THROTTLE), slot: "
                                         + slot);
                             }
                             break;
                         case WeaverReadStatus.FAILED:
                             response[0] = VerifyCredentialResponse.ERROR;
-                            Log.e(TAG, "weaver read failed (FAILED), slot: " + slot);
+                            Slog.e(TAG, "weaver read failed (FAILED), slot: " + slot);
                             break;
                         default:
                             response[0] = VerifyCredentialResponse.ERROR;
-                            Log.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
+                            Slog.e(TAG, "weaver read unknown status " + status + ", slot: " + slot);
                             break;
                     }
                 });
         } catch (RemoteException e) {
             response[0] = VerifyCredentialResponse.ERROR;
-            Log.e(TAG, "weaver read failed, slot: " + slot, e);
+            Slog.e(TAG, "weaver read failed, slot: " + slot, e);
         }
         return response[0];
     }
@@ -437,13 +435,20 @@
         }
     }
 
-    public int getCredentialType(long handle, int userId) {
+    int getCredentialType(long handle, int userId) {
         byte[] passwordData = loadState(PASSWORD_DATA_NAME, handle, userId);
         if (passwordData == null) {
-            Log.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
+            Slog.w(TAG, "getCredentialType: encountered empty password data for user " + userId);
             return LockPatternUtils.CREDENTIAL_TYPE_NONE;
         }
-        return PasswordData.fromBytes(passwordData).passwordType;
+        return PasswordData.fromBytes(passwordData).credentialType;
+    }
+
+    static int getFrpCredentialType(byte[] payload) {
+        if (payload == null) {
+            return LockPatternUtils.CREDENTIAL_TYPE_NONE;
+        }
+        return PasswordData.fromBytes(payload).credentialType;
     }
 
     /**
@@ -469,17 +474,18 @@
      *
      */
     public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
-            byte[] hash, byte[] credential, int userId) {
+            byte[] hash, LockscreenCredential credential, int userId) {
         AuthenticationToken result = AuthenticationToken.create();
         GateKeeperResponse response;
         if (hash != null) {
             try {
-                response = gatekeeper.enroll(userId, hash, credential, result.deriveGkPassword());
+                response = gatekeeper.enroll(userId, hash, credential.getCredential(),
+                        result.deriveGkPassword());
             } catch (RemoteException e) {
                 throw new IllegalStateException("Failed to enroll credential duing SP init", e);
             }
             if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
-                Log.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
+                Slog.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
                 clearSidForUser(userId);
             } else {
                 saveSyntheticPasswordHandle(response.getPayload(), userId);
@@ -504,7 +510,7 @@
             throw new IllegalStateException("Failed to create new SID for user", e);
         }
         if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
-            Log.e(TAG, "Fail to create new SID for user " + userId);
+            Slog.e(TAG, "Fail to create new SID for user " + userId);
             return;
         }
         saveSyntheticPasswordHandle(response.getPayload(), userId);
@@ -561,7 +567,7 @@
         buffer.put(data, 0, data.length);
         buffer.flip();
         if (buffer.get() != WEAVER_VERSION) {
-            Log.e(TAG, "Invalid weaver slot version of handle " + handle);
+            Slog.e(TAG, "Invalid weaver slot version of handle " + handle);
             return INVALID_WEAVER_SLOT;
         }
         return buffer.getInt();
@@ -580,11 +586,11 @@
         if (slot != INVALID_WEAVER_SLOT) {
             Set<Integer> usedSlots = getUsedWeaverSlots();
             if (!usedSlots.contains(slot)) {
-                Log.i(TAG, "Destroy weaver slot " + slot + " for user " + userId);
+                Slog.i(TAG, "Destroy weaver slot " + slot + " for user " + userId);
                 weaverEnroll(slot, null, null);
                 mPasswordSlotManager.markSlotDeleted(slot);
             } else {
-                Log.w(TAG, "Skip destroying reused weaver slot " + slot + " for user " + userId);
+                Slog.w(TAG, "Skip destroying reused weaver slot " + slot + " for user " + userId);
             }
         }
     }
@@ -638,15 +644,9 @@
      * @throw IllegalStateException if creation fails.
      */
     public long createPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
-            byte[] credential, int credentialType, AuthenticationToken authToken,
-            int requestedQuality, int userId) {
-        if (credential == null || credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
-            credentialType = LockPatternUtils.CREDENTIAL_TYPE_NONE;
-            credential = DEFAULT_PASSWORD;
-        }
-
+            LockscreenCredential credential, AuthenticationToken authToken, int userId) {
         long handle = generateHandle();
-        PasswordData pwd = PasswordData.create(credentialType);
+        PasswordData pwd = PasswordData.create(credential.getType());
         byte[] pwdToken = computePasswordToken(credential, pwd);
         final long sid;
         final byte[] applicationId;
@@ -654,7 +654,7 @@
         if (isWeaverAvailable()) {
             // Weaver based user password
             int weaverSlot = getNextAvailableWeaverSlot();
-            Log.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId);
+            Slog.i(TAG, "Weaver enroll password to slot " + weaverSlot + " for user " + userId);
             byte[] weaverSecret = weaverEnroll(weaverSlot, passwordTokenToWeaverKey(pwdToken),
                     null);
             if (weaverSecret == null) {
@@ -663,7 +663,8 @@
             }
             saveWeaverSlot(weaverSlot, handle, userId);
             mPasswordSlotManager.markSlotInUse(weaverSlot);
-            synchronizeWeaverFrpPassword(pwd, requestedQuality, userId, weaverSlot);
+            // No need to pass in quality since the credential type already encodes sufficient info
+            synchronizeWeaverFrpPassword(pwd, 0, userId, weaverSlot);
 
             pwd.passwordHandle = null;
             sid = GateKeeper.INVALID_SECURE_USER_ID;
@@ -674,7 +675,7 @@
             try {
                 gatekeeper.clearSecureUserId(fakeUid(userId));
             } catch (RemoteException ignore) {
-                Log.w(TAG, "Failed to clear SID from gatekeeper");
+                Slog.w(TAG, "Failed to clear SID from gatekeeper");
             }
             // GateKeeper based user password
             GateKeeperResponse response;
@@ -692,7 +693,8 @@
             sid = sidFromPasswordHandle(pwd.passwordHandle);
             applicationId = transformUnderSecdiscardable(pwdToken,
                     createSecdiscardable(handle, userId));
-            synchronizeFrpPassword(pwd, requestedQuality, userId);
+            // No need to pass in quality since the credential type already encodes sufficient info
+            synchronizeFrpPassword(pwd, 0, userId);
         }
         saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
 
@@ -702,7 +704,7 @@
     }
 
     public VerifyCredentialResponse verifyFrpCredential(IGateKeeperService gatekeeper,
-            byte[] userCredential, int credentialType,
+            LockscreenCredential userCredential,
             ICheckCredentialProgressCallback progressCallback) {
         PersistentData persistentData = mStorage.readPersistentDataBlock();
         if (persistentData.type == PersistentData.TYPE_SP) {
@@ -714,7 +716,7 @@
                 response = gatekeeper.verifyChallenge(fakeUid(persistentData.userId),
                         0 /* challenge */, pwd.passwordHandle, passwordTokenToGkInput(pwdToken));
             } catch (RemoteException e) {
-                Log.e(TAG, "FRP verifyChallenge failed", e);
+                Slog.e(TAG, "FRP verifyChallenge failed", e);
                 return VerifyCredentialResponse.ERROR;
             }
             return VerifyCredentialResponse.fromGateKeeperResponse(response);
@@ -725,7 +727,7 @@
 
             return weaverVerify(weaverSlot, passwordTokenToWeaverKey(pwdToken)).stripPayload();
         } else {
-            Log.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
+            Slog.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
                     + persistentData.type);
             return VerifyCredentialResponse.ERROR;
         }
@@ -733,11 +735,11 @@
 
 
     public void migrateFrpPasswordLocked(long handle, UserInfo userInfo, int requestedQuality) {
-        if (mStorage.getPersistentDataBlock() != null
+        if (mStorage.getPersistentDataBlockManager() != null
                 && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
             PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle,
                     userInfo.id));
-            if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+            if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                 int weaverSlot = loadWeaverSlot(handle, userInfo.id);
                 if (weaverSlot != INVALID_WEAVER_SLOT) {
                     synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot);
@@ -750,10 +752,10 @@
 
     private void synchronizeFrpPassword(PasswordData pwd,
             int requestedQuality, int userId) {
-        if (mStorage.getPersistentDataBlock() != null
+        if (mStorage.getPersistentDataBlockManager() != null
                 && LockPatternUtils.userOwnsFrpCredential(mContext,
                 mUserManager.getUserInfo(userId))) {
-            if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+            if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
                         pwd.toBytes());
             } else {
@@ -764,10 +766,10 @@
 
     private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
             int weaverSlot) {
-        if (mStorage.getPersistentDataBlock() != null
+        if (mStorage.getPersistentDataBlockManager() != null
                 && LockPatternUtils.userOwnsFrpCredential(mContext,
                 mUserManager.getUserInfo(userId))) {
-            if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+            if (pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
                         requestedQuality, pwd.toBytes());
             } else {
@@ -829,14 +831,14 @@
             return false;
         }
         if (!loadEscrowData(authToken, userId)) {
-            Log.w(TAG, "User is not escrowable");
+            Slog.w(TAG, "User is not escrowable");
             return false;
         }
         if (isWeaverAvailable()) {
             int slot = getNextAvailableWeaverSlot();
-            Log.i(TAG, "Weaver enroll token to slot " + slot + " for user " + userId);
+            Slog.i(TAG, "Weaver enroll token to slot " + slot + " for user " + userId);
             if (weaverEnroll(slot, null, tokenData.weaverSecret) == null) {
-                Log.e(TAG, "Failed to enroll weaver secret when activating token");
+                Slog.e(TAG, "Failed to enroll weaver secret when activating token");
                 return false;
             }
             saveWeaverSlot(slot, handle, userId);
@@ -881,18 +883,20 @@
      * Decrypt a synthetic password by supplying the user credential and corresponding password
      * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
      * verification to referesh the SID & Auth token maintained by the system.
-     * Note: the credential type is not validated here since there are call sites where the type is
-     * unknown. Caller might choose to validate it by examining AuthenticationResult.credentialType
      */
     public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
-            long handle, byte[] credential, int userId,
+            long handle, @NonNull LockscreenCredential credential, int userId,
             ICheckCredentialProgressCallback progressCallback) {
-        if (credential == null) {
-            credential = DEFAULT_PASSWORD;
-        }
         AuthenticationResult result = new AuthenticationResult();
         PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle, userId));
-        result.credentialType = pwd.passwordType;
+
+        if (!credential.checkAgainstStoredType(pwd.credentialType)) {
+            Slog.e(TAG, String.format("Credential type mismatch: expected %d actual %d",
+                    pwd.credentialType, credential.getType()));
+            result.gkResponse = VerifyCredentialResponse.ERROR;
+            return result;
+        }
+
         byte[] pwdToken = computePasswordToken(credential, pwd);
 
         final byte[] applicationId;
@@ -901,7 +905,7 @@
         if (weaverSlot != INVALID_WEAVER_SLOT) {
             // Weaver based user password
             if (!isWeaverAvailable()) {
-                Log.e(TAG, "No weaver service to unwrap password based SP");
+                Slog.e(TAG, "No weaver service to unwrap password based SP");
                 result.gkResponse = VerifyCredentialResponse.ERROR;
                 return result;
             }
@@ -918,7 +922,7 @@
                 response = gatekeeper.verifyChallenge(fakeUid(userId), 0L,
                         pwd.passwordHandle, gkPwdToken);
             } catch (RemoteException e) {
-                Log.e(TAG, "gatekeeper verify failed", e);
+                Slog.e(TAG, "gatekeeper verify failed", e);
                 result.gkResponse = VerifyCredentialResponse.ERROR;
                 return result;
             }
@@ -931,21 +935,19 @@
                         reenrollResponse = gatekeeper.enroll(fakeUid(userId),
                                 pwd.passwordHandle, gkPwdToken, gkPwdToken);
                     } catch (RemoteException e) {
-                        Log.w(TAG, "Fail to invoke gatekeeper.enroll", e);
+                        Slog.w(TAG, "Fail to invoke gatekeeper.enroll", e);
                         reenrollResponse = GateKeeperResponse.ERROR;
                         // continue the flow anyway
                     }
                     if (reenrollResponse.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
                         pwd.passwordHandle = reenrollResponse.getPayload();
+                        // Use the reenrollment opportunity to update credential type
+                        // (getting rid of CREDENTIAL_TYPE_PASSWORD_OR_PIN)
+                        pwd.credentialType = credential.getType();
                         saveState(PASSWORD_DATA_NAME, pwd.toBytes(), handle, userId);
-                        synchronizeFrpPassword(pwd,
-                                pwd.passwordType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN
-                                ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
-                                : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
-                                /* TODO(roosa): keep the same password quality */,
-                                userId);
+                        synchronizeFrpPassword(pwd, 0, userId);
                     } else {
-                        Log.w(TAG, "Fail to re-enroll user password for user " + userId);
+                        Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
                         // continue the flow anyway
                     }
                 }
@@ -966,7 +968,7 @@
             try {
                 progressCallback.onCredentialVerified();
             } catch (RemoteException e) {
-                Log.w(TAG, "progressCallback throws exception", e);
+                Slog.w(TAG, "progressCallback throws exception", e);
             }
         }
         result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
@@ -989,14 +991,14 @@
         int slotId = loadWeaverSlot(handle, userId);
         if (slotId != INVALID_WEAVER_SLOT) {
             if (!isWeaverAvailable()) {
-                Log.e(TAG, "No weaver service to unwrap token based SP");
+                Slog.e(TAG, "No weaver service to unwrap token based SP");
                 result.gkResponse = VerifyCredentialResponse.ERROR;
                 return result;
             }
             VerifyCredentialResponse response = weaverVerify(slotId, null);
             if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK ||
                     response.getPayload() == null) {
-                Log.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
+                Slog.e(TAG, "Failed to retrieve weaver secret when unwrapping token");
                 result.gkResponse = VerifyCredentialResponse.ERROR;
                 return result;
             }
@@ -1043,13 +1045,13 @@
                 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
         }
         if (secret == null) {
-            Log.e(TAG, "Fail to decrypt SP for user " + userId);
+            Slog.e(TAG, "Fail to decrypt SP for user " + userId);
             return null;
         }
         AuthenticationToken result = new AuthenticationToken(version);
         if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
             if (!loadEscrowData(result, userId)) {
-                Log.e(TAG, "User is not escrowable: " + userId);
+                Slog.e(TAG, "User is not escrowable: " + userId);
                 return null;
             }
             result.recreate(secret);
@@ -1057,7 +1059,7 @@
             result.syntheticPassword = new String(secret);
         }
         if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
-            Log.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
+            Slog.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
             createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
         }
         return result;
@@ -1084,7 +1086,7 @@
             response = gatekeeper.verifyChallenge(userId, challenge,
                     spHandle, auth.deriveGkPassword());
         } catch (RemoteException e) {
-            Log.e(TAG, "Fail to verify with gatekeeper " + userId, e);
+            Slog.e(TAG, "Fail to verify with gatekeeper " + userId, e);
             return VerifyCredentialResponse.ERROR;
         }
         int responseCode = response.getResponseCode();
@@ -1095,7 +1097,7 @@
                     response = gatekeeper.enroll(userId, spHandle, spHandle,
                             auth.deriveGkPassword());
                 } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to invoke gatekeeper.enroll", e);
+                    Slog.e(TAG, "Failed to invoke gatekeeper.enroll", e);
                     response = GateKeeperResponse.ERROR;
                 }
                 if (response.getResponseCode() == GateKeeperResponse.RESPONSE_OK) {
@@ -1105,7 +1107,7 @@
                     return verifyChallenge(gatekeeper, auth, challenge, userId);
                 } else {
                     // Fall through, return result from the previous verification attempt.
-                    Log.w(TAG, "Fail to re-enroll SP handle for user " + userId);
+                    Slog.w(TAG, "Fail to re-enroll SP handle for user " + userId);
                 }
             }
             return result;
@@ -1225,7 +1227,8 @@
         return String.format("%s%x", LockPatternUtils.SYNTHETIC_PASSWORD_KEY_PREFIX, handle);
     }
 
-    private byte[] computePasswordToken(byte[] password, PasswordData data) {
+    private byte[] computePasswordToken(LockscreenCredential credential, PasswordData data) {
+        final byte[] password = credential.isNone() ? DEFAULT_PASSWORD : credential.getCredential();
         return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP,
                 PASSWORD_TOKEN_LENGTH);
     }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 5676da2..29338ba0 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -18,7 +18,6 @@
 
 import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.os.RemoteException;
 import android.security.Scrypt;
@@ -193,6 +192,7 @@
     private boolean isCustomLockScreen() {
         return mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE
             && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+            && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PIN
             && mCredentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
     }
 
@@ -345,7 +345,7 @@
         }
         KeyChainProtectionParams keyChainProtectionParams = new KeyChainProtectionParams.Builder()
                 .setUserSecretType(TYPE_LOCKSCREEN)
-                .setLockScreenUiFormat(getUiFormat(mCredentialType, mCredential))
+                .setLockScreenUiFormat(getUiFormat(mCredentialType))
                 .setKeyDerivationParams(keyDerivationParams)
                 .setSecret(new byte[0])
                 .build();
@@ -449,11 +449,10 @@
      * @return The format - either pattern, pin, or password.
      */
     @VisibleForTesting
-    @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(
-            int credentialType, byte[] credential) {
+    @KeyChainProtectionParams.LockScreenUiFormat static int getUiFormat(int credentialType) {
         if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
             return KeyChainProtectionParams.UI_FORMAT_PATTERN;
-        } else if (isPin(credential)) {
+        } else if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PIN) {
             return KeyChainProtectionParams.UI_FORMAT_PIN;
         } else {
             return KeyChainProtectionParams.UI_FORMAT_PASSWORD;
@@ -472,23 +471,6 @@
     }
 
     /**
-     * Returns {@code true} if {@code credential} looks like a pin.
-     */
-    @VisibleForTesting
-    static boolean isPin(@Nullable byte[] credential) {
-        if (credential == null) {
-            return false;
-        }
-        int length = credential.length;
-        for (int i = 0; i < length; i++) {
-            if (!Character.isDigit((char) credential[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
      * Hashes {@code credentials} with the given {@code salt}.
      *
      * @return The SHA-256 hash.
@@ -541,6 +523,7 @@
     }
 
     private boolean shouldUseScryptToHashCredential() {
-        return mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+        return mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                || mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_PIN;
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java
index 90a36723..c963f79 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelper.java
@@ -97,7 +97,8 @@
         if (credential == null) {
             return false;
         }
-        if (credentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+        if (credentialType != LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+                && credentialType != LockPatternUtils.CREDENTIAL_TYPE_PIN) {
             return false;
         }
         byte[] insecurePasswordPrefixBytes =
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 714bbb9..9a1b30d 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -25,7 +25,6 @@
 import android.os.BugreportParams;
 import android.os.IDumpstate;
 import android.os.IDumpstateListener;
-import android.os.IDumpstateToken;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -64,13 +63,6 @@
 
     @Override
     @RequiresPermission(android.Manifest.permission.DUMP)
-    public IDumpstateToken setListener(String name, IDumpstateListener listener,
-            boolean getSectionDetails) {
-        throw new UnsupportedOperationException("setListener is not allowed on this service");
-    }
-
-    @Override
-    @RequiresPermission(android.Manifest.permission.DUMP)
     public void startBugreport(int callingUidUnused, String callingPackage,
             FileDescriptor bugreportFd, FileDescriptor screenshotFd,
             int bugreportMode, IDumpstateListener listener) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 00c0566..ed2bb3d5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -259,7 +259,7 @@
         // Don't hold mSessions lock when calling restoreSession, since it might trigger an APK
         // atomic install which needs to query sessions, which requires lock on mSessions.
         for (PackageInstallerSession session : stagedSessionsToRestore) {
-            if (mPm.isDeviceUpgrading()) {
+            if (mPm.isDeviceUpgrading() && !session.isStagedAndInTerminalState()) {
                 session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
                         "Build fingerprint has changed");
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0a4415b..104ce1c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1562,8 +1562,8 @@
                     }
                     // Send broadcasts
                     for (int i = 0; i < size; i++) {
-                        sendPackageChangedBroadcast(packages[i], true, components[i], uids[i],
-                                null);
+                        sendPackageChangedBroadcast(packages[i], true /* dontKillApp */,
+                                components[i], uids[i], null /* reason */);
                     }
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                     break;
@@ -2049,7 +2049,7 @@
                 for (int i = 0; i < res.libraryConsumers.size(); i++) {
                     PackageParser.Package pkg = res.libraryConsumers.get(i);
                     // send broadcast that all consumers of the static shared library have changed
-                    sendPackageChangedBroadcast(pkg.packageName, false /*killFlag*/,
+                    sendPackageChangedBroadcast(pkg.packageName, false /* dontKillApp */,
                             new ArrayList<>(Collections.singletonList(pkg.packageName)),
                             pkg.applicationInfo.uid, null);
                 }
@@ -19999,7 +19999,7 @@
     }
 
     private void sendPackageChangedBroadcast(String packageName,
-            boolean killFlag, ArrayList<String> componentNames, int packageUid,
+            boolean dontKillApp, ArrayList<String> componentNames, int packageUid,
             String reason) {
         if (DEBUG_INSTALL)
             Log.v(TAG, "Sending package changed: package=" + packageName + " components="
@@ -20009,7 +20009,7 @@
         String nameList[] = new String[componentNames.size()];
         componentNames.toArray(nameList);
         extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
-        extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
+        extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, dontKillApp);
         extras.putInt(Intent.EXTRA_UID, packageUid);
         if (reason != null) {
             extras.putString(Intent.EXTRA_REASON, reason);
@@ -20282,7 +20282,7 @@
                     return;
                 }
                 sendPackageChangedBroadcast(pkg.packageName,
-                        false /* killFlag */,
+                        true /* dontKillApp */,
                         new ArrayList<>(Collections.singletonList(pkg.packageName)),
                         pkg.applicationInfo.uid,
                         Intent.ACTION_OVERLAY_CHANGED);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9e88d0c..bb3388c 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -53,7 +53,6 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
-import android.app.AppOpsManager;
 import android.app.ApplicationPackageManager;
 import android.app.IActivityManager;
 import android.content.Context;
@@ -1542,7 +1541,6 @@
             }
         };
 
-        final AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
         for (int i = 0; i < permissionCount; i++) {
             final String permName = pkg.requestedPermissions.get(i);
             final BasePermission bp;
@@ -1608,16 +1606,9 @@
 
             // If this permission was granted by default, make sure it is.
             if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0) {
+                // PermissionPolicyService will handle the app op for runtime permissions later.
                 grantRuntimePermissionInternal(permName, packageName, false,
                         Process.SYSTEM_UID, userId, delayingPermCallback);
-                // Allow app op later as we are holding mPackages
-                // PermissionPolicyService will handle the app op for foreground/background
-                // permissions.
-                String appOp = AppOpsManager.permissionToOp(permName);
-                if (appOp != null) {
-                    mHandler.post(() -> appOpsManager.setUidMode(appOp, uid,
-                            AppOpsManager.MODE_ALLOWED));
-                }
             // If permission review is enabled the permissions for a legacy apps
             // are represented as constantly granted runtime ones, so don't revoke.
             } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
@@ -4233,8 +4224,8 @@
         }
 
         @Override
-        public @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtectionLevel(
-                @PermissionInfo.Protection int protectionLevel) {
+        public @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtection(
+                @PermissionInfo.Protection int protection) {
             ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
 
             synchronized (PermissionManagerService.this.mLock) {
@@ -4244,7 +4235,7 @@
                     BasePermission bp = mSettings.mPermissions.valueAt(i);
 
                     if (bp.perm != null && bp.perm.info != null
-                            && bp.protectionLevel == protectionLevel) {
+                            && bp.perm.info.getProtection() == protection) {
                         matchingPermissions.add(bp.perm.info);
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 04ec5ba..a807a7e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -291,8 +291,8 @@
     public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
 
     /** Get all permission that have a certain protection level */
-    public abstract @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtectionLevel(
-            @PermissionInfo.Protection int protectionLevel);
+    public abstract @NonNull ArrayList<PermissionInfo> getAllPermissionWithProtection(
+            @PermissionInfo.Protection int protection);
 
     /**
      * Returns the delegate used to influence permission checking.
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index b892360..9b9f93f 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -48,12 +48,12 @@
 import android.permission.PermissionControllerManager;
 import android.provider.Telephony;
 import android.telecom.TelecomManager;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LongSparseLongArray;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsCallback;
@@ -67,6 +67,7 @@
 import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -144,7 +145,7 @@
         };
 
         final ArrayList<PermissionInfo> dangerousPerms =
-                permManagerInternal.getAllPermissionWithProtectionLevel(
+                permManagerInternal.getAllPermissionWithProtection(
                         PermissionInfo.PROTECTION_DANGEROUS);
 
         try {
@@ -152,7 +153,7 @@
             for (int i = 0; i < numDangerousPerms; i++) {
                 PermissionInfo perm = dangerousPerms.get(i);
 
-                if (perm.isRestricted() || perm.backgroundPermission != null) {
+                if (perm.isRuntime()) {
                     appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener);
                 }
                 if (perm.isSoftRestricted()) {
@@ -389,8 +390,7 @@
         private final @NonNull PackageManager mPackageManager;
         private final @NonNull AppOpsManager mAppOpsManager;
 
-        /** All uid that need to be synchronized */
-        private final @NonNull SparseIntArray mAllUids = new SparseIntArray();
+        private final @NonNull ArrayMap<String, PermissionInfo> mRuntimePermissionInfos;
 
         /**
          * All ops that need to be flipped to allow.
@@ -428,6 +428,18 @@
             mContext = context;
             mPackageManager = context.getPackageManager();
             mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+            mRuntimePermissionInfos = new ArrayMap<>();
+            PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
+                    PermissionManagerServiceInternal.class);
+            List<PermissionInfo> permissionInfos =
+                    permissionManagerInternal.getAllPermissionWithProtection(
+                            PermissionInfo.PROTECTION_DANGEROUS);
+            int permissionInfosSize = permissionInfos.size();
+            for (int i = 0; i < permissionInfosSize; i++) {
+                PermissionInfo permissionInfo = permissionInfos.get(i);
+                mRuntimePermissionInfos.put(permissionInfo.name, permissionInfo);
+            }
         }
 
         /**
@@ -489,7 +501,7 @@
          * Note: Called with the package lock held. Do <u>not</u> call into app-op manager.
          */
         private void addAppOps(@NonNull PackageInfo packageInfo, @NonNull String permissionName) {
-            PermissionInfo permissionInfo = getPermissionInfo(permissionName);
+            PermissionInfo permissionInfo = mRuntimePermissionInfos.get(permissionName);
             if (permissionInfo == null) {
                 return;
             }
@@ -499,8 +511,7 @@
 
         private void addPermissionAppOp(@NonNull PackageInfo packageInfo,
                 @NonNull PermissionInfo permissionInfo) {
-            // TODO: Sync all permissions in the future.
-            if (!permissionInfo.isRestricted() && permissionInfo.backgroundPermission == null) {
+            if (!permissionInfo.isRuntime()) {
                 return;
             }
 
@@ -525,7 +536,7 @@
             boolean shouldGrantAppOp = shouldGrantAppOp(packageInfo, permissionInfo);
             if (shouldGrantAppOp) {
                 if (permissionInfo.backgroundPermission != null) {
-                    PermissionInfo backgroundPermissionInfo = getPermissionInfo(
+                    PermissionInfo backgroundPermissionInfo = mRuntimePermissionInfos.get(
                             permissionInfo.backgroundPermission);
                     boolean shouldGrantBackgroundAppOp = backgroundPermissionInfo != null
                             && shouldGrantAppOp(packageInfo, backgroundPermissionInfo);
@@ -552,15 +563,6 @@
             }
         }
 
-        @Nullable
-        private PermissionInfo getPermissionInfo(@NonNull String permissionName) {
-            try {
-                return mPackageManager.getPermissionInfo(permissionName, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                return null;
-            }
-        }
-
         private boolean shouldGrantAppOp(@NonNull PackageInfo packageInfo,
                 @NonNull PermissionInfo permissionInfo) {
             String permissionName = permissionInfo.name;
@@ -638,8 +640,6 @@
                 return;
             }
 
-            mAllUids.put(pkg.applicationInfo.uid, pkg.applicationInfo.uid);
-
             if (pkg.requestedPermissions == null) {
                 return;
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2593c38..5e1d93f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -386,6 +386,7 @@
     BurnInProtectionHelper mBurnInProtectionHelper;
     private DisplayFoldController mDisplayFoldController;
     AppOpsManager mAppOpsManager;
+    PackageManager mPackageManager;
     private boolean mHasFeatureAuto;
     private boolean mHasFeatureWatch;
     private boolean mHasFeatureLeanback;
@@ -1555,10 +1556,9 @@
     private void launchAllAppsAction() {
         Intent intent = new Intent(Intent.ACTION_ALL_APPS);
         if (mHasFeatureLeanback) {
-            final PackageManager pm = mContext.getPackageManager();
             Intent intentLauncher = new Intent(Intent.ACTION_MAIN);
             intentLauncher.addCategory(Intent.CATEGORY_HOME);
-            ResolveInfo resolveInfo = pm.resolveActivityAsUser(intentLauncher,
+            ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(intentLauncher,
                     PackageManager.MATCH_SYSTEM_ONLY,
                     mCurrentUserId);
             if (resolveInfo != null) {
@@ -1753,10 +1753,11 @@
         mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
-        mHasFeatureWatch = mContext.getPackageManager().hasSystemFeature(FEATURE_WATCH);
-        mHasFeatureLeanback = mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK);
-        mHasFeatureAuto = mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE);
-        mHasFeatureHdmiCec = mContext.getPackageManager().hasSystemFeature(FEATURE_HDMI_CEC);
+        mPackageManager = mContext.getPackageManager();
+        mHasFeatureWatch = mPackageManager.hasSystemFeature(FEATURE_WATCH);
+        mHasFeatureLeanback = mPackageManager.hasSystemFeature(FEATURE_LEANBACK);
+        mHasFeatureAuto = mPackageManager.hasSystemFeature(FEATURE_AUTOMOTIVE);
+        mHasFeatureHdmiCec = mPackageManager.hasSystemFeature(FEATURE_HDMI_CEC);
         mAccessibilityShortcutController =
                 new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
         mLogger = new MetricsLogger();
@@ -1994,7 +1995,7 @@
         }
 
         mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_NOTHING;
-        if (mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+        if (mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
             mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
         }
     }
@@ -2138,7 +2139,7 @@
 
         ApplicationInfo appInfo;
         try {
-            appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+            appInfo = mPackageManager.getApplicationInfoAsUser(
                             attrs.packageName,
                             0 /* flags */,
                             UserHandle.getUserId(callingUid));
@@ -4914,7 +4915,7 @@
             @Override public void run() {
                 if (mBootMsgDialog == null) {
                     int theme;
-                    if (mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK)) {
+                    if (mPackageManager.hasSystemFeature(FEATURE_LEANBACK)) {
                         theme = com.android.internal.R.style.Theme_Leanback_Dialog_Alert;
                     } else {
                         theme = 0;
@@ -4943,7 +4944,7 @@
                             return true;
                         }
                     };
-                    if (mContext.getPackageManager().isDeviceUpgrading()) {
+                    if (mPackageManager.isDeviceUpgrading()) {
                         mBootMsgDialog.setTitle(R.string.android_upgrading_title);
                     } else {
                         mBootMsgDialog.setTitle(R.string.android_start_title);
@@ -5203,7 +5204,7 @@
         }
 
         ActivityInfo ai = null;
-        ResolveInfo info = mContext.getPackageManager().resolveActivityAsUser(
+        ResolveInfo info = mPackageManager.resolveActivityAsUser(
                 intent,
                 PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
                 mCurrentUserId);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index edf0cbf..b67d9b2 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -94,16 +94,16 @@
     private static final int MSG_PROFILE_TIMED_OUT = 5;
     private static final int MSG_WIRED_CHARGING_STARTED = 6;
 
-    private static final long[] WIRELESS_VIBRATION_TIME = {
+    private static final long[] CHARGING_VIBRATION_TIME = {
             40, 40, 40, 40, 40, 40, 40, 40, 40, // ramp-up sampling rate = 40ms
             40, 40, 40, 40, 40, 40, 40 // ramp-down sampling rate = 40ms
     };
-    private static final int[] WIRELESS_VIBRATION_AMPLITUDE = {
+    private static final int[] CHARGING_VIBRATION_AMPLITUDE = {
             1, 4, 11, 25, 44, 67, 91, 114, 123, // ramp-up amplitude (from 0 to 50%)
             103, 79, 55, 34, 17, 7, 2 // ramp-up amplitude
     };
-    private static final VibrationEffect WIRELESS_CHARGING_VIBRATION_EFFECT =
-            VibrationEffect.createWaveform(WIRELESS_VIBRATION_TIME, WIRELESS_VIBRATION_AMPLITUDE,
+    private static final VibrationEffect CHARGING_VIBRATION_EFFECT =
+            VibrationEffect.createWaveform(CHARGING_VIBRATION_TIME, CHARGING_VIBRATION_AMPLITUDE,
                     -1);
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -130,6 +130,10 @@
     // True if the device should suspend when the screen is off due to proximity.
     private final boolean mSuspendWhenScreenOffDueToProximityConfig;
 
+    // True if the device should show the wireless charging animation when the device
+    // begins charging wirelessly
+    private final boolean mShowWirelessChargingAnimationConfig;
+
     // The current interactive state.  This is set as soon as an interactive state
     // transition begins so as to capture the reason that it happened.  At some point
     // this state will propagate to the pending state then eventually to the
@@ -182,6 +186,8 @@
 
         mSuspendWhenScreenOffDueToProximityConfig = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
+        mShowWirelessChargingAnimationConfig = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim);
 
         // Initialize interactive state for battery stats.
         try {
@@ -755,35 +761,45 @@
         }
     };
 
-    /**
-     * If enabled, plays a sound and/or vibration when wireless or non-wireless charging has started
-     */
-    private void playChargingStartedFeedback(@UserIdInt int userId) {
-        playChargingStartedVibration(userId);
+    private void playChargingStartedFeedback(@UserIdInt int userId, boolean wireless) {
+        if (!isChargingFeedbackEnabled(userId)) {
+            return;
+        }
+
+        // vibrate
+        final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
+        if (vibrate) {
+            mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, VIBRATION_ATTRIBUTES);
+        }
+
+        // play sound
         final String soundPath = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.CHARGING_STARTED_SOUND);
-        if (isChargingFeedbackEnabled(userId) && soundPath != null) {
-            final Uri soundUri = Uri.parse("file://" + soundPath);
-            if (soundUri != null) {
-                final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
-                if (sfx != null) {
-                    sfx.setStreamType(AudioManager.STREAM_SYSTEM);
-                    sfx.play();
-                }
+                wireless ? Settings.Global.WIRELESS_CHARGING_STARTED_SOUND
+                        : Settings.Global.CHARGING_STARTED_SOUND);
+        final Uri soundUri = Uri.parse("file://" + soundPath);
+        if (soundUri != null) {
+            final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
+            if (sfx != null) {
+                sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+                sfx.play();
             }
         }
     }
 
     private void showWirelessChargingStarted(int batteryLevel, @UserIdInt int userId) {
-        playChargingStartedFeedback(userId);
-        if (mStatusBarManagerInternal != null) {
+        // play sounds + haptics
+        playChargingStartedFeedback(userId, true /* wireless */);
+
+        // show animation
+        if (mShowWirelessChargingAnimationConfig && mStatusBarManagerInternal != null) {
             mStatusBarManagerInternal.showChargingAnimation(batteryLevel);
         }
         mSuspendBlocker.release();
     }
 
     private void showWiredChargingStarted(@UserIdInt int userId) {
-        playChargingStartedFeedback(userId);
+        playChargingStartedFeedback(userId, false /* wireless */);
         mSuspendBlocker.release();
     }
 
@@ -791,14 +807,6 @@
         mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
     }
 
-    private void playChargingStartedVibration(@UserIdInt int userId) {
-        final boolean vibrateEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
-        if (vibrateEnabled && isChargingFeedbackEnabled(userId)) {
-            mVibrator.vibrate(WIRELESS_CHARGING_VIBRATION_EFFECT, VIBRATION_ATTRIBUTES);
-        }
-    }
-
     private boolean isChargingFeedbackEnabled(@UserIdInt int userId) {
         final boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.CHARGING_SOUNDS_ENABLED, 1, userId) != 0;
diff --git a/services/core/java/com/android/server/protolog/ProtoLogImpl.java b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
index 20bab55..1653b3d 100644
--- a/services/core/java/com/android/server/protolog/ProtoLogImpl.java
+++ b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
@@ -45,7 +45,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.IllegalFormatConversionException;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
@@ -57,8 +56,18 @@
 public class ProtoLogImpl {
     private static final TreeMap<String, IProtoLogGroup> LOG_GROUPS = new TreeMap<>();
 
+    /**
+     * A runnable to update the cached output of {@link #isEnabled}.
+     *
+     * Must be invoked after every action that could change the result of {@link #isEnabled}, eg.
+     * starting / stopping proto log, or enabling / disabling log groups.
+     */
+    static Runnable sCacheUpdater = () -> { };
+
     private static void addLogGroupEnum(IProtoLogGroup[] config) {
-        Arrays.stream(config).forEach(group -> LOG_GROUPS.put(group.name(), group));
+        for (IProtoLogGroup group : config) {
+            LOG_GROUPS.put(group.name(), group);
+        }
     }
 
     static {
@@ -112,7 +121,7 @@
 
     /** Returns true iff logging is enabled for the given {@code IProtoLogGroup}. */
     public static boolean isEnabled(IProtoLogGroup group) {
-        return group.isLogToProto()
+        return group.isLogToLogcat()
                 || (group.isLogToProto() && getSingleInstance().isProtoEnabled());
     }
 
@@ -303,6 +312,7 @@
             mProtoLogEnabled = true;
             mProtoLogEnabledLockFree = true;
         }
+        sCacheUpdater.run();
     }
 
     /**
@@ -327,6 +337,7 @@
                 throw new IllegalStateException("logging enabled while waiting for flush.");
             }
         }
+        sCacheUpdater.run();
     }
 
     /**
@@ -351,6 +362,7 @@
                 return -1;
             }
         }
+        sCacheUpdater.run();
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3c1a6af..489c343 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -655,11 +655,11 @@
     }
 
     @Override
-    public void onBiometricAuthenticated(boolean authenticated, String failureReason) {
+    public void onBiometricAuthenticated() {
         enforceBiometricDialog();
         if (mBar != null) {
             try {
-                mBar.onBiometricAuthenticated(authenticated, failureReason);
+                mBar.onBiometricAuthenticated();
             } catch (RemoteException ex) {
             }
         }
@@ -677,11 +677,11 @@
     }
 
     @Override
-    public void onBiometricError(int errorCode, String error) {
+    public void onBiometricError(int modality, int error, int vendorCode) {
         enforceBiometricDialog();
         if (mBar != null) {
             try {
-                mBar.onBiometricError(errorCode, error);
+                mBar.onBiometricError(modality, error, vendorCode);
             } catch (RemoteException ex) {
             }
         }
diff --git a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
index 7bdc8a3..9dbbf16 100644
--- a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Intent;
 import android.util.Slog;
 import android.util.TimestampedValue;
@@ -48,9 +48,8 @@
     // @NonNull after initialize()
     private Callback mCallback;
 
-    // NITZ state.
-    @Nullable private TimestampedValue<Long> mLastNitzTime;
-
+    // Last phone suggestion.
+    @Nullable private PhoneTimeSuggestion mLastPhoneSuggestion;
 
     // Information about the last time signal received: Used when toggling auto-time.
     @Nullable private TimestampedValue<Long> mLastSystemClockTime;
@@ -65,46 +64,40 @@
     }
 
     @Override
-    public void suggestTime(@NonNull TimeSignal timeSignal) {
-        if (!TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId())) {
-            Slog.w(TAG, "Ignoring signal from unsupported source: " + timeSignal);
-            return;
-        }
-
+    public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
         // NITZ logic
 
-        TimestampedValue<Long> newNitzUtcTime = timeSignal.getUtcTime();
-        boolean nitzTimeIsValid = validateNewNitzTime(newNitzUtcTime, mLastNitzTime);
-        if (!nitzTimeIsValid) {
+        boolean timeSuggestionIsValid =
+                validateNewPhoneSuggestion(timeSuggestion, mLastPhoneSuggestion);
+        if (!timeSuggestionIsValid) {
             return;
         }
         // Always store the last NITZ value received, regardless of whether we go on to use it to
         // update the system clock. This is so that we can validate future NITZ signals.
-        mLastNitzTime = newNitzUtcTime;
+        mLastPhoneSuggestion = timeSuggestion;
 
         // System clock update logic.
 
         // Historically, Android has sent a telephony broadcast only when setting the time using
         // NITZ.
-        final boolean sendNetworkBroadcast =
-                TimeSignal.SOURCE_ID_NITZ.equals(timeSignal.getSourceId());
+        final boolean sendNetworkBroadcast = true;
 
-        final TimestampedValue<Long> newUtcTime = newNitzUtcTime;
+        final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime();
         setSystemClockIfRequired(newUtcTime, sendNetworkBroadcast);
     }
 
-    private static boolean validateNewNitzTime(TimestampedValue<Long> newNitzUtcTime,
-            TimestampedValue<Long> lastNitzTime) {
+    private static boolean validateNewPhoneSuggestion(@NonNull PhoneTimeSuggestion newSuggestion,
+            @Nullable PhoneTimeSuggestion lastSuggestion) {
 
-        if (lastNitzTime != null) {
-            long referenceTimeDifference =
-                    TimestampedValue.referenceTimeDifference(newNitzUtcTime, lastNitzTime);
+        if (lastSuggestion != null) {
+            long referenceTimeDifference = TimestampedValue.referenceTimeDifference(
+                    newSuggestion.getUtcTime(), lastSuggestion.getUtcTime());
             if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) {
                 // Out of order or bogus.
                 Slog.w(TAG, "validateNewNitzTime: Bad NITZ signal received."
                         + " referenceTimeDifference=" + referenceTimeDifference
-                        + " lastNitzTime=" + lastNitzTime
-                        + " newNitzUtcTime=" + newNitzUtcTime);
+                        + " lastSuggestion=" + lastSuggestion
+                        + " newSuggestion=" + newSuggestion);
                 return false;
             }
         }
@@ -182,7 +175,7 @@
 
     @Override
     public void dump(@NonNull PrintWriter pw, @Nullable String[] args) {
-        pw.println("mLastNitzTime=" + mLastNitzTime);
+        pw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion);
         pw.println("mLastSystemClockTimeSet=" + mLastSystemClockTimeSet);
         pw.println("mLastSystemClockTime=" + mLastSystemClockTime);
         pw.println("mLastSystemClockTimeSendNetworkBroadcast="
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 9c83000..ee42279 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.timedetector.ITimeDetectorService;
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -96,14 +96,14 @@
     }
 
     @Override
-    public void suggestTime(@NonNull TimeSignal timeSignal) {
+    public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSignal) {
         enforceSetTimePermission();
         Objects.requireNonNull(timeSignal);
 
         long idToken = Binder.clearCallingIdentity();
         try {
             synchronized (mStrategyLock) {
-                mTimeDetectorStrategy.suggestTime(timeSignal);
+                mTimeDetectorStrategy.suggestPhoneTime(timeSignal);
             }
         } finally {
             Binder.restoreCallingIdentity(idToken);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index e050865..7c2a945 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Intent;
 import android.util.TimestampedValue;
 
@@ -72,7 +72,7 @@
     void initialize(@NonNull Callback callback);
 
     /** Process the suggested time. */
-    void suggestTime(@NonNull TimeSignal timeSignal);
+    void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion);
 
     /** Handle the auto-time setting being toggled on or off. */
     void handleAutoTimeDetectionToggle(boolean enabled);
diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
index e4cb19e..e72ba8d 100644
--- a/services/core/java/com/android/server/twilight/TwilightService.java
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -17,6 +17,7 @@
 package com.android.server.twilight;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -160,8 +161,13 @@
         // Request the device's location immediately if a previous location isn't available.
         if (mLocationManager.getLastLocation() == null) {
             if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
-                mLocationManager.requestSingleUpdate(
-                        LocationManager.NETWORK_PROVIDER, this, Looper.getMainLooper());
+                mLocationManager.getCurrentLocation(
+                        LocationManager.NETWORK_PROVIDER, null, getContext().getMainExecutor(),
+                        this::onLocationChanged);
+            } else if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+                mLocationManager.getCurrentLocation(
+                        LocationManager.GPS_PROVIDER, null, getContext().getMainExecutor(),
+                        this::onLocationChanged);
             }
         }
 
@@ -218,12 +224,7 @@
                 for (int i = mListeners.size() - 1; i >= 0; --i) {
                     final TwilightListener listener = mListeners.keyAt(i);
                     final Handler handler = mListeners.valueAt(i);
-                    handler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            listener.onTwilightStateChanged(state);
-                        }
-                    });
+                    handler.post(() -> listener.onTwilightStateChanged(state));
                 }
             }
         }
@@ -243,12 +244,8 @@
     }
 
     @Override
-    public void onLocationChanged(Location location) {
-        // Location providers may erroneously return (0.0, 0.0) when they fail to determine the
-        // device's location. These location updates can be safely ignored since the chance of a
-        // user actually being at these coordinates is quite low.
-        if (location != null
-                && !(location.getLongitude() == 0.0 && location.getLatitude() == 0.0)) {
+    public void onLocationChanged(@Nullable Location location) {
+        if (location != null) {
             Slog.d(TAG, "onLocationChanged:"
                     + " provider=" + location.getProvider()
                     + " accuracy=" + location.getAccuracy()
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index e6c6b12e..9d41d97 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -187,9 +187,22 @@
         private int startingWindowDelayMs = INVALID_DELAY;
         private int bindApplicationDelayMs = INVALID_DELAY;
         private int reason = APP_TRANSITION_TIMEOUT;
-        private boolean loggedWindowsDrawn;
+        // TODO(b/132736359) The number may need to consider the visibility change.
+        private int numUndrawnActivities = 1;
         private boolean loggedStartingWindowDrawn;
         private boolean launchTraceActive;
+
+        /**
+         * Remembers the latest launched activity to represent the final transition. This also
+         * increments the number of activities that should be drawn, so a consecutive launching
+         * sequence can be coalesced as one event.
+         */
+        void setLatestLaunchedActivity(ActivityRecord r) {
+            if (launchedActivity == r) {
+                return;
+            }
+            launchedActivity = r;
+        }
     }
 
     final class WindowingModeTransitionInfoSnapshot {
@@ -400,7 +413,7 @@
             // the other attributes.
 
             // Coalesce multiple (trampoline) activities from a single sequence together.
-            info.launchedActivity = launchedActivity;
+            info.setLatestLaunchedActivity(launchedActivity);
             return;
         }
 
@@ -422,7 +435,7 @@
         // A new launch sequence [with the windowingMode] has begun.
         // Start tracking it.
         final WindowingModeTransitionInfo newInfo = new WindowingModeTransitionInfo();
-        newInfo.launchedActivity = launchedActivity;
+        newInfo.setLatestLaunchedActivity(launchedActivity);
         newInfo.currentTransitionProcessRunning = processRunning;
         newInfo.startResult = resultCode;
         mWindowingModeTransitionInfo.put(windowingMode, newInfo);
@@ -448,11 +461,11 @@
         if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn windowingMode=" + windowingMode);
 
         final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
-        if (info == null || info.loggedWindowsDrawn) {
+        if (info == null || info.numUndrawnActivities == 0) {
             return null;
         }
         info.windowsDrawnDelayMs = calculateDelay(timestampNs);
-        info.loggedWindowsDrawn = true;
+        info.numUndrawnActivities--;
         final WindowingModeTransitionInfoSnapshot infoSnapshot =
                 new WindowingModeTransitionInfoSnapshot(info);
         if (allWindowsDrawn() && mLoggedTransitionStarting) {
@@ -594,9 +607,10 @@
         }
     }
 
-    private boolean allWindowsDrawn() {
+    @VisibleForTesting
+    boolean allWindowsDrawn() {
         for (int index = mWindowingModeTransitionInfo.size() - 1; index >= 0; index--) {
-            if (!mWindowingModeTransitionInfo.valueAt(index).loggedWindowsDrawn) {
+            if (mWindowingModeTransitionInfo.valueAt(index).numUndrawnActivities != 0) {
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 1bc36bb..887ece5 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2086,7 +2086,6 @@
         final TaskRecord task = r.getTaskRecord();
         final ActivityStack stack = task.getStack();
 
-        r.mLaunchTaskBehind = false;
         mRecentTasks.add(task);
         mService.getTaskChangeNotificationController().notifyTaskStackChanged();
         stack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 208f54c..a783ee9 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -876,6 +876,8 @@
                 if (canToastShowWhenLocked(callingPid)) {
                     attrs.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
                 }
+                // Toasts can't be clickable
+                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
                 break;
         }
 
@@ -3241,7 +3243,8 @@
                 statusBar.topAppWindowChanged(displayId, isFullscreen, isImmersive);
 
                 // TODO(b/118118435): Remove this after removing system UI visibilities.
-                mDisplayContent.statusBarVisibilityChanged(visibility);
+                mDisplayContent.statusBarVisibilityChanged(
+                        visibility & ~(View.STATUS_BAR_UNHIDE | View.NAVIGATION_BAR_UNHIDE));
             }
         });
         return diff;
diff --git a/services/core/java/com/android/server/wm/ProtoLogGroup.java b/services/core/java/com/android/server/wm/ProtoLogGroup.java
index f5a1884..5c2830e 100644
--- a/services/core/java/com/android/server/wm/ProtoLogGroup.java
+++ b/services/core/java/com/android/server/wm/ProtoLogGroup.java
@@ -129,7 +129,7 @@
     private static class Consts {
         private static final String TAG_WM = "WindowManager";
 
-        private static final boolean ENABLE_DEBUG = false;
+        private static final boolean ENABLE_DEBUG = true;
         private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
     }
 }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 12579e6..b7d25c3 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -247,7 +247,7 @@
                     "startRecentsActivity");
             mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
                     this, mDefaultDisplay.mDisplayId,
-                    mStackSupervisor.mRecentTasks.getRecentTaskIds());
+                    mStackSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
 
             // If we updated the launch-behind state, update the visibility of the activities after
             // we fetch the visible tasks to be controlled by the animation
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 9a58a68..7a3e43b 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -358,7 +358,8 @@
      * because it may call cancelAnimation() which needs to properly clean up the controller
      * in the window manager.
      */
-    public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds) {
+    public void initialize(int targetActivityType, SparseBooleanArray recentTaskIds,
+            ActivityRecord targetActivity) {
         mTargetActivityType = targetActivityType;
         mDisplayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
 
@@ -400,16 +401,12 @@
         }
 
         // Adjust the wallpaper visibility for the showing target activity
-        final ActivityRecord recentsComponentActivity =
-                targetStack.getTopChild().getTopFullscreenActivity();
-        if (recentsComponentActivity != null) {
-            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
-                    "setHomeApp(%s)", recentsComponentActivity.getName());
-            mTargetActivityRecord = recentsComponentActivity;
-            if (recentsComponentActivity.windowsCanBeWallpaperTarget()) {
-                mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-                mDisplayContent.setLayoutNeeded();
-            }
+        ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
+                "setHomeApp(%s)", targetActivity.getName());
+        mTargetActivityRecord = targetActivity;
+        if (targetActivity.windowsCanBeWallpaperTarget()) {
+            mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            mDisplayContent.setLayoutNeeded();
         }
 
         // Save the minimized home height
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e90f3da..63ce1b1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2652,11 +2652,11 @@
     void initializeRecentsAnimation(int targetActivityType,
             IRecentsAnimationRunner recentsAnimationRunner,
             RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId,
-            SparseBooleanArray recentTaskIds) {
+            SparseBooleanArray recentTaskIds, ActivityRecord targetActivity) {
         mRecentsAnimationController = new RecentsAnimationController(this, recentsAnimationRunner,
                 callbacks, displayId);
         mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
-        mRecentsAnimationController.initialize(targetActivityType, recentTaskIds);
+        mRecentsAnimationController.initialize(targetActivityType, recentTaskIds, targetActivity);
     }
 
     @VisibleForTesting
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c4adc035..5e49c7a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -168,6 +168,7 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.net.ConnectivityManager;
@@ -1959,6 +1960,10 @@
             return mContext.getSystemService(ConnectivityManager.class);
         }
 
+        LocationManager getLocationManager() {
+            return mContext.getSystemService(LocationManager.class);
+        }
+
         IWindowManager getIWindowManager() {
             return IWindowManager.Stub
                     .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
@@ -4178,7 +4183,7 @@
     private boolean passwordQualityInvocationOrderCheckEnabled(String packageName, int userId) {
         try {
             return mIPlatformCompat.isChangeEnabledByPackageName(ADMIN_APP_PASSWORD_COMPLEXITY,
-                    packageName);
+                    packageName, userId);
         } catch (RemoteException e) {
             Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
         }
@@ -10899,6 +10904,36 @@
     }
 
     @Override
+    public void setLocationEnabled(ComponentName who, boolean locationEnabled) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        int userId = mInjector.userHandleGetCallingUserId();
+
+        synchronized (getLockObject()) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            if (!isDeviceOwner(who, userId) && !isCurrentUserDemo()) {
+                throw new SecurityException(
+                        "Permission denial: Profile owners cannot update location settings");
+            }
+        }
+
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            mInjector.getLocationManager().setLocationEnabledForUser(
+                    locationEnabled, UserHandle.of(userId));
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_SECURE_SETTING)
+                    .setAdmin(who)
+                    .setStrings(Settings.Secure.LOCATION_MODE, Integer.toString(
+                            locationEnabled ? Settings.Secure.LOCATION_MODE_ON
+                                    : Settings.Secure.LOCATION_MODE_OFF))
+                    .write();
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public boolean setTime(ComponentName who, long millis) {
         Preconditions.checkNotNull(who, "ComponentName is null in setTime");
         enforceDeviceOwner(who);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 88859a7..5883048 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -90,6 +90,7 @@
 import com.android.server.camera.CameraServiceProxy;
 import com.android.server.clipboard.ClipboardService;
 import com.android.server.compat.PlatformCompat;
+import com.android.server.compat.PlatformCompatNative;
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.coverage.CoverageService;
@@ -641,8 +642,10 @@
         // Platform compat service is used by ActivityManagerService, PackageManagerService, and
         // possibly others in the future. b/135010838.
         t.traceBegin("PlatformCompat");
-        ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE,
-                new PlatformCompat(mSystemContext));
+        PlatformCompat platformCompat = new PlatformCompat(mSystemContext);
+        ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
+        ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
+                new PlatformCompatNative(platformCompat));
         t.traceEnd();
 
         // Wait for installd to finish starting up so that it has a chance to
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 4aeeb0a..ec47a95 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -33,7 +33,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -43,17 +42,15 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricServiceReceiverInternal;
-import android.hardware.face.FaceManager;
-import android.hardware.face.IFaceService;
 import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintService;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.security.KeyStore;
 
 import androidx.test.InstrumentationRegistry;
@@ -68,8 +65,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
-
 @SmallTest
 public class BiometricServiceTest {
 
@@ -98,71 +93,33 @@
     @Mock
     private PackageManager mPackageManager;
     @Mock
-    private AppOpsManager mAppOpsManager;
-    @Mock
     IBiometricServiceReceiver mReceiver1;
     @Mock
     IBiometricServiceReceiver mReceiver2;
     @Mock
-    FingerprintManager mFingerprintManager;
+    BiometricService.Injector mInjector;
     @Mock
-    FaceManager mFaceManager;
-
-    private static class MockInjector extends BiometricService.Injector {
-        @Override
-        IActivityManager getActivityManagerService() {
-            return mock(IActivityManager.class);
-        }
-
-        @Override
-        IStatusBarService getStatusBarService() {
-            return mock(IStatusBarService.class);
-        }
-
-        @Override
-        IFingerprintService getFingerprintService() {
-            return mock(IFingerprintService.class);
-        }
-
-        @Override
-        IFaceService getFaceService() {
-            return mock(IFaceService.class);
-        }
-
-        @Override
-        BiometricService.SettingObserver getSettingObserver(Context context, Handler handler,
-                List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
-            return mock(BiometricService.SettingObserver.class);
-        }
-
-        @Override
-        KeyStore getKeyStore() {
-            return mock(KeyStore.class);
-        }
-
-        @Override
-        boolean isDebugEnabled(Context context, int userId) {
-            return false;
-        }
-
-        @Override
-        void publishBinderService(BiometricService service, IBiometricService.Stub impl) {
-            // no-op for test
-        }
-    }
+    IBiometricAuthenticator mFingerprintAuthenticator;
+    @Mock
+    IBiometricAuthenticator mFaceAuthenticator;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
-        when(mContext.getSystemService(Context.FINGERPRINT_SERVICE))
-                .thenReturn(mFingerprintManager);
-        when(mContext.getSystemService(Context.FACE_SERVICE)).thenReturn(mFaceManager);
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getResources()).thenReturn(mResources);
 
+        when(mInjector.getActivityManagerService()).thenReturn(mock(IActivityManager.class));
+        when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
+        when(mInjector.getFingerprintAuthenticator()).thenReturn(mFingerprintAuthenticator);
+        when(mInjector.getFaceAuthenticator()).thenReturn(mFaceAuthenticator);
+        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(mResources.getString(R.string.biometric_error_hw_unavailable))
                 .thenReturn(ERROR_HW_UNAVAILABLE);
         when(mResources.getString(R.string.biometric_not_recognized))
@@ -172,61 +129,69 @@
     }
 
     @Test
-    public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws Exception {
+    public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws
+            Exception {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
                 .thenReturn(false);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)).thenReturn(false);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
 
-        mBiometricService = new BiometricService(mContext, new MockInjector());
+        mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 false /* allowDeviceCredential */);
         waitForIdle();
         verify(mReceiver1).onError(
-                eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT), eq(ERROR_HW_UNAVAILABLE));
+                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+                eq(0 /* vendorCode */));
     }
 
     @Test
     public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
-        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
 
-        mBiometricService = new BiometricService(mContext, new MockInjector());
+        mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 false /* allowDeviceCredential */);
         waitForIdle();
         verify(mReceiver1).onError(
-                eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS), any());
+                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+                eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS),
+                eq(0 /* vendorCode */));
     }
 
     @Test
-    public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws Exception {
+    public void testAuthenticate_whenHalIsDead_returnsErrorHardwareUnavailable() throws
+            Exception {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
-        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
-        when(mFingerprintManager.isHardwareDetected()).thenReturn(false);
+        when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+        when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(false);
 
-        mBiometricService = new BiometricService(mContext, new MockInjector());
+        mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 false /* allowDeviceCredential */);
         waitForIdle();
         verify(mReceiver1).onError(
-                eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
+                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
+                eq(0 /* vendorCode */));
     }
 
     @Test
     public void testAuthenticateFace_respectsUserSetting()
             throws Exception {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
-        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
-        when(mFaceManager.isHardwareDetected()).thenReturn(true);
+        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+        when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
 
-        mBiometricService = new BiometricService(mContext, new MockInjector());
+        mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
         // Disabled in user settings receives onError
@@ -235,7 +200,9 @@
                 false /* allowDeviceCredential */);
         waitForIdle();
         verify(mReceiver1).onError(
-                eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(ERROR_HW_UNAVAILABLE));
+                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
+                eq(0 /* vendorCode */));
 
         // Enrolled, not disabled in settings, user requires confirmation in settings
         resetReceiver();
@@ -245,8 +212,8 @@
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 false /* allowDeviceCredential */);
         waitForIdle();
-        verify(mReceiver1, never()).onError(anyInt(), any(String.class));
-        verify(mBiometricService.mFaceService).prepareForAuthentication(
+        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+        verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
                 eq(true) /* requireConfirmation */,
                 any(IBinder.class),
                 anyLong() /* sessionId */,
@@ -265,7 +232,7 @@
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
                 false /* allowDeviceCredential */);
         waitForIdle();
-        verify(mBiometricService.mFaceService).prepareForAuthentication(
+        verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
                 eq(false) /* requireConfirmation */,
                 any(IBinder.class),
                 anyLong() /* sessionId */,
@@ -281,7 +248,7 @@
     @Test
     public void testAuthenticate_happyPathWithoutConfirmation() throws Exception {
         setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
-        mBiometricService = new BiometricService(mContext, new MockInjector());
+        mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
         // Start testing the happy path
@@ -295,8 +262,9 @@
 
         // Invokes <Modality>Service#prepareForAuthentication
         ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mReceiver1, never()).onError(anyInt(), any(String.class));
-        verify(mBiometricService.mFingerprintService).prepareForAuthentication(
+        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+        verify(mBiometricService.mAuthenticators.get(0).impl).prepareForAuthentication(
+                anyBoolean() /* requireConfirmation */,
                 any(IBinder.class),
                 anyLong() /* sessionId */,
                 anyInt() /* userId */,
@@ -316,7 +284,7 @@
                 BiometricService.STATE_AUTH_STARTED);
 
         // startPreparedClient invoked
-        verify(mBiometricService.mFingerprintService)
+        verify(mBiometricService.mAuthenticators.get(0).impl)
                 .startPreparedClient(cookieCaptor.getValue());
 
         // StatusBar showBiometricDialog invoked
@@ -337,8 +305,7 @@
         assertEquals(mBiometricService.mCurrentAuthSession.mState,
                 BiometricService.STATE_AUTHENTICATED_PENDING_SYSUI);
         // Notify SystemUI hardware authenticated
-        verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(
-                eq(true) /* authenticated */, eq(null) /* failureReason */);
+        verify(mBiometricService.mStatusBarService).onBiometricAuthenticated();
 
         // SystemUI sends callback with dismissed reason
         mBiometricService.mInternalReceiver.onDialogDismissed(
@@ -355,7 +322,7 @@
     @Test
     public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
         setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
-        when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(false);
+        when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */, true /* allowDeviceCredential */);
         waitForIdle();
@@ -409,8 +376,10 @@
         mBiometricService.mInternalReceiver.onAuthenticationFailed();
         waitForIdle();
 
-        verify(mBiometricService.mStatusBarService)
-                .onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
+        verify(mBiometricService.mStatusBarService).onBiometricError(
+                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
+                eq(0 /* vendorCode */));
         verify(mReceiver1).onAuthenticationFailed();
         assertEquals(mBiometricService.mCurrentAuthSession.mState,
                 BiometricService.STATE_AUTH_PAUSED);
@@ -426,15 +395,18 @@
         mBiometricService.mInternalReceiver.onAuthenticationFailed();
         waitForIdle();
 
-        verify(mBiometricService.mStatusBarService)
-                .onBiometricAuthenticated(eq(false), eq(ERROR_NOT_RECOGNIZED));
+        verify(mBiometricService.mStatusBarService).onBiometricError(
+                eq(BiometricAuthenticator.TYPE_NONE),
+                eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
+                eq(0 /* vendorCode */));
         verify(mReceiver1).onAuthenticationFailed();
         assertEquals(mBiometricService.mCurrentAuthSession.mState,
                 BiometricService.STATE_AUTH_STARTED);
     }
 
     @Test
-    public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws Exception {
+    public void testErrorCanceled_whenAuthenticating_notifiesSystemUIAndClient() throws
+            Exception {
         setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, false /* allowDeviceCredential */);
@@ -451,15 +423,15 @@
                 BiometricService.STATE_AUTH_STARTED);
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
-                BiometricConstants.BIOMETRIC_ERROR_CANCELED, ERROR_CANCELED);
+                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(), anyString());
-        verify(mReceiver2, never()).onError(anyInt(), any(String.class));
+        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
+        verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
 
         // SystemUI dialog closed
         verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
@@ -469,8 +441,9 @@
                 .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
         waitForIdle();
         verify(mReceiver1).onError(
+                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
-                eq(ERROR_CANCELED));
+                eq(0 /* vendorCode */));
         assertNull(mBiometricService.mCurrentAuthSession);
     }
 
@@ -482,14 +455,17 @@
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FACE,
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
-                ERROR_TIMEOUT);
+                0 /* vendorCode */);
         waitForIdle();
 
         assertEquals(mBiometricService.mCurrentAuthSession.mState,
                 BiometricService.STATE_AUTH_PAUSED);
-        verify(mBiometricService.mStatusBarService)
-                .onBiometricAuthenticated(eq(false), eq(ERROR_TIMEOUT));
+        verify(mBiometricService.mStatusBarService).onBiometricError(
+                eq(BiometricAuthenticator.TYPE_FACE),
+                eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
+                eq(0 /* vendorCode */));
         // Timeout does not count as fail as per BiometricPrompt documentation.
         verify(mReceiver1, never()).onAuthenticationFailed();
 
@@ -524,22 +500,25 @@
     public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
         setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
-                false /* requireCOnfirmation */, false /* allowDeviceCredential */);
+                false /* requireConfirmation */, false /* allowDeviceCredential */);
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FACE,
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
-                ERROR_TIMEOUT);
+                0 /* vendorCode */);
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FACE,
                 BiometricConstants.BIOMETRIC_ERROR_CANCELED,
-                ERROR_CANCELED);
+                0 /* vendorCode */);
         waitForIdle();
 
         // Client receives error immediately
         verify(mReceiver1).onError(
+                eq(BiometricAuthenticator.TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
-                eq(ERROR_CANCELED));
+                eq(0 /* vendorCode */));
         // Dialog is hidden immediately
         verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
         // Auth session is over
@@ -558,26 +537,29 @@
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FINGERPRINT,
                 BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
-                ERROR_UNABLE_TO_PROCESS);
+                0 /* vendorCode */);
         waitForIdle();
 
         // Sends error to SystemUI and does not notify client yet
         assertEquals(mBiometricService.mCurrentAuthSession.mState,
                 BiometricService.STATE_ERROR_PENDING_SYSUI);
         verify(mBiometricService.mStatusBarService).onBiometricError(
+                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
-                eq(ERROR_UNABLE_TO_PROCESS));
+                eq(0 /* vendorCode */));
         verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
-        verify(mReceiver1, never()).onError(anyInt(), anyString());
+        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
 
         // SystemUI animation completed, client is notified, auth session is over
         mBiometricService.mInternalReceiver
                 .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
         waitForIdle();
         verify(mReceiver1).onError(
+                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
-                eq(ERROR_UNABLE_TO_PROCESS));
+                eq(0 /* vendorCode */));
         assertNull(mBiometricService.mCurrentAuthSession);
     }
 
@@ -590,8 +572,9 @@
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForPendingSession(mBiometricService.mPendingAuthSession),
+                BiometricAuthenticator.TYPE_FACE,
                 BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
-                ERROR_LOCKOUT);
+                0 /* vendorCode */);
         waitForIdle();
 
         // Pending auth session becomes current auth session, since device credential should
@@ -622,8 +605,9 @@
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForPendingSession(mBiometricService.mPendingAuthSession),
+                BiometricAuthenticator.TYPE_FINGERPRINT,
                 BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
-                ERROR_LOCKOUT);
+                0 /* vendorCode */);
         waitForIdle();
 
         // Error is sent to client
@@ -690,17 +674,18 @@
 
         assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
                 mBiometricService.mCurrentAuthSession.mState);
-        verify(mReceiver1, never()).onError(anyInt(), anyString());
+        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FINGERPRINT,
                 BiometricConstants.BIOMETRIC_ERROR_CANCELED,
-                ERROR_CANCELED);
+                0 /* vendorCode */);
         waitForIdle();
 
         assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
                 mBiometricService.mCurrentAuthSession.mState);
-        verify(mReceiver1, never()).onError(anyInt(), anyString());
+        verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
     }
 
     @Test
@@ -714,15 +699,17 @@
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FINGERPRINT,
                 BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
-                ERROR_LOCKOUT);
+                0 /* vendorCode */);
         waitForIdle();
 
         assertEquals(BiometricService.STATE_SHOWING_DEVICE_CREDENTIAL,
                 mBiometricService.mCurrentAuthSession.mState);
         verify(mBiometricService.mStatusBarService).onBiometricError(
+                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT),
-                eq(ERROR_LOCKOUT));
+                eq(0 /* vendorCode */));
     }
 
     @Test
@@ -736,15 +723,17 @@
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FINGERPRINT,
                 BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
-                ERROR_UNABLE_TO_PROCESS);
+                0 /* vendorCode */);
         waitForIdle();
 
         assertEquals(BiometricService.STATE_ERROR_PENDING_SYSUI,
                 mBiometricService.mCurrentAuthSession.mState);
         verify(mBiometricService.mStatusBarService).onBiometricError(
+                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
-                eq(ERROR_UNABLE_TO_PROCESS));
+                eq(0 /* vendorCode */));
     }
 
     @Test
@@ -758,9 +747,10 @@
                 .onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
         waitForIdle();
         verify(mReceiver1).onError(
+                eq(BiometricAuthenticator.TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
-                eq(ERROR_USER_CANCELED));
-        verify(mBiometricService.mFingerprintService).cancelAuthenticationFromService(
+                eq(0 /* vendorCode */));
+        verify(mBiometricService.mAuthenticators.get(0).impl).cancelAuthenticationFromService(
                 any(),
                 any(),
                 anyInt(),
@@ -778,13 +768,15 @@
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FACE,
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
-                ERROR_TIMEOUT);
+                0 /* vendorCode */);
         mBiometricService.mInternalReceiver.onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_NEGATIVE);
         waitForIdle();
 
-        verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
+        verify(mBiometricService.mAuthenticators.get(0).impl,
+                never()).cancelAuthenticationFromService(
                 any(),
                 any(),
                 anyInt(),
@@ -794,20 +786,23 @@
     }
 
     @Test
-    public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws Exception {
+    public void testDismissedReasonUserCancel_whilePaused_doesntInvokeHalCancel() throws
+            Exception {
         setupAuthForOnly(BiometricAuthenticator.TYPE_FACE);
         invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */, false /* allowDeviceCredential */);
 
         mBiometricService.mInternalReceiver.onError(
                 getCookieForCurrentSession(mBiometricService.mCurrentAuthSession),
+                BiometricAuthenticator.TYPE_FACE,
                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT,
-                ERROR_TIMEOUT);
+                0 /* vendorCode */);
         mBiometricService.mInternalReceiver.onDialogDismissed(
                 BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
         waitForIdle();
 
-        verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
+        verify(mBiometricService.mAuthenticators.get(0).impl,
+                never()).cancelAuthenticationFromService(
                 any(),
                 any(),
                 anyInt(),
@@ -830,7 +825,8 @@
         waitForIdle();
 
         // doesn't send cancel to HAL
-        verify(mBiometricService.mFaceService, never()).cancelAuthenticationFromService(
+        verify(mBiometricService.mAuthenticators.get(0).impl,
+                never()).cancelAuthenticationFromService(
                 any(),
                 any(),
                 anyInt(),
@@ -838,8 +834,9 @@
                 anyInt(),
                 anyBoolean());
         verify(mReceiver1).onError(
+                eq(BiometricAuthenticator.TYPE_FACE),
                 eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
-                eq(ERROR_USER_CANCELED));
+                eq(0 /* vendorCode */));
         assertNull(mBiometricService.mCurrentAuthSession);
     }
 
@@ -863,7 +860,7 @@
 
     // Helper methods
 
-    private void setupAuthForOnly(int modality) {
+    private void setupAuthForOnly(int modality) throws RemoteException {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
                 .thenReturn(false);
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(false);
@@ -871,17 +868,17 @@
         if (modality == BiometricAuthenticator.TYPE_FINGERPRINT) {
             when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
                     .thenReturn(true);
-            when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
-            when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+            when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+            when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
         } else if (modality == BiometricAuthenticator.TYPE_FACE) {
             when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
-            when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
-            when(mFaceManager.isHardwareDetected()).thenReturn(true);
+            when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+            when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
         } else {
             fail("Unknown modality: " + modality);
         }
 
-        mBiometricService = new BiometricService(mContext, new MockInjector());
+        mBiometricService = new BiometricService(mContext, mInjector);
         mBiometricService.onStart();
 
         when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 537287d..9863057 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -52,6 +52,7 @@
 import com.android.internal.widget.ILockSettings;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.server.LocalServices;
 import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
 import com.android.server.wm.WindowManagerInternal;
@@ -104,6 +105,7 @@
     FaceManager mFaceManager;
     PackageManager mPackageManager;
     protected boolean mHasSecureLockScreen;
+    FakeSettings mSettings;
 
     @Override
     protected void setUp() throws Exception {
@@ -125,6 +127,7 @@
         mFingerprintManager = mock(FingerprintManager.class);
         mFaceManager = mock(FaceManager.class);
         mPackageManager = mock(PackageManager.class);
+        mSettings = new FakeSettings();
 
         LocalServices.removeServiceForTest(LockSettingsInternal.class);
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -162,7 +165,7 @@
         mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage,
                 mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
                 mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
-                mUserManagerInternal, mDeviceStateCache);
+                mUserManagerInternal, mDeviceStateCache, mSettings);
         when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
         mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
         installChildProfile(MANAGED_PROFILE_USER_ID);
@@ -195,6 +198,7 @@
         mockBiometricsHardwareFingerprintsAndTemplates(PRIMARY_USER_ID);
         mockBiometricsHardwareFingerprintsAndTemplates(MANAGED_PROFILE_USER_ID);
 
+        mSettings.setDeviceProvisioned(true);
         mLocalService = LocalServices.getService(LockSettingsInternal.class);
     }
 
@@ -307,4 +311,22 @@
     protected static void assertArrayNotEquals(byte[] expected, byte[] actual) {
         assertFalse(Arrays.equals(expected, actual));
     }
+
+    protected LockscreenCredential newPassword(String password) {
+        return LockscreenCredential.createPasswordOrNone(password);
+    }
+
+    protected LockscreenCredential newPin(String pin) {
+        return LockscreenCredential.createPinOrNone(pin);
+    }
+
+    protected LockscreenCredential newPattern(String pattern) {
+        return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+                pattern.getBytes()));
+    }
+
+    protected LockscreenCredential nonePassword() {
+        return LockscreenCredential.createNone();
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
index d2a9145..5c54883 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/CachedSyntheticPasswordTests.java
@@ -15,9 +15,6 @@
  */
 package com.android.server.locksettings;
 
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
 import static org.mockito.Mockito.anyInt;
@@ -30,7 +27,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 
 import org.mockito.ArgumentCaptor;
@@ -59,56 +56,53 @@
     }
 
     public void testSyntheticPasswordClearCredentialUntrusted() throws RemoteException {
-        final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
-        final byte[] newPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
+        final LockscreenCredential password = newPassword("password");
+        final LockscreenCredential newPassword = newPassword("newpassword");
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         // clear password
-        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, null,
-                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, true);
+        mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID, true);
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
 
         // set a new password
-        mService.setLockCredential(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(newPassword,
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+        mService.setLockCredential(newPassword, nonePassword(), PRIMARY_USER_ID,
+                false);
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                newPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
         assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
     public void testSyntheticPasswordChangeCredentialUntrusted() throws RemoteException {
-        final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
-        final byte[] newPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
+        final LockscreenCredential password = newPassword("password");
+        final LockscreenCredential newPassword = newPassword("newpassword");
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         // Untrusted change password
-        mService.setLockCredential(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, true);
+        mService.setLockCredential(newPassword, nonePassword(), PRIMARY_USER_ID,
+                true);
         assertNotEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
 
         // Verify the password
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(newPassword,
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode());
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                newPassword, 0, PRIMARY_USER_ID).getResponseCode());
     }
 
     public void testUntrustedCredentialChangeMaintainsAuthSecret() throws RemoteException {
-        final byte[] password =
-                "testUntrustedCredentialChangeMaintainsAuthSecret-password".getBytes();
-        final byte[] newPassword =
-                "testUntrustedCredentialChangeMaintainsAuthSecret-newpassword".getBytes();
+        final LockscreenCredential password = newPassword("password");
+        final LockscreenCredential newPassword = newPassword("newpassword");
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         // Untrusted change password
-        mService.setLockCredential(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, true);
+        mService.setLockCredential(newPassword, nonePassword(), PRIMARY_USER_ID,
+                true);
 
         // Verify the password
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(newPassword,
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                newPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
 
         // Ensure the same secret was passed each time
@@ -118,31 +112,30 @@
     }
 
     public void testUntrustedCredentialChangeBlockedIfSpNotCached() throws RemoteException {
-        final byte[] password =
-                "testUntrustedCredentialChangeBlockedIfSpNotCached-password".getBytes();
-        final byte[] newPassword =
-                "testUntrustedCredentialChangeBlockedIfSpNotCached-newpassword".getBytes();
+        final LockscreenCredential password = newPassword("password");
+        final LockscreenCredential newPassword = newPassword("newpassword");
 
         // Disable caching for this test
         enableSpCaching(false);
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        flushHandlerTasks();
+
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         // Untrusted change password
         assertExpectException(
                 IllegalStateException.class,
                 /* messageRegex= */ "Untrusted credential reset not possible without cached SP",
-                () -> mService.setLockCredential(newPassword,
-                        LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                        PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, true));
+                () -> mService.setLockCredential(newPassword, nonePassword(),
+                        PRIMARY_USER_ID, true));
         assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
 
         // Verify the new password doesn't work but the old one still does
-        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(newPassword,
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
+                newPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password,
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, 0, PRIMARY_USER_ID)
                         .getResponseCode());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
new file mode 100644
index 0000000..70a927c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
@@ -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 com.android.server.locksettings;
+
+import android.provider.Settings;
+
+public class FakeSettings {
+
+    private int mDeviceProvisioned;
+
+    public void setDeviceProvisioned(boolean provisioned) {
+        mDeviceProvisioned = provisioned ? 1 : 0;
+    }
+
+    public int globalGetInt(String keyName) {
+        switch (keyName) {
+            case Settings.Global.DEVICE_PROVISIONED:
+                return mDeviceProvisioned;
+            default:
+                throw new IllegalArgumentException("Unhandled global settings: " + keyName);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index fcd98e0..7e7e170 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -20,9 +20,11 @@
 
 import android.app.IActivityManager;
 import android.app.admin.DeviceStateCache;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.hardware.authsecret.V1_0.IAuthSecret;
 import android.os.Handler;
+import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserManagerInternal;
@@ -31,6 +33,7 @@
 import android.security.keystore.KeyPermanentlyInvalidatedException;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.server.ServiceThread;
 import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
 
@@ -50,12 +53,14 @@
         private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
         private UserManagerInternal mUserManagerInternal;
         private DeviceStateCache mDeviceStateCache;
+        private FakeSettings mSettings;
 
         public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
                 IActivityManager activityManager, LockPatternUtils lockPatternUtils,
                 IStorageManager storageManager, SyntheticPasswordManager spManager,
                 FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
-                UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
+                UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
+                FakeSettings settings) {
             super(context);
             mLockSettingsStorage = storage;
             mKeyStore = keyStore;
@@ -67,6 +72,7 @@
             mRecoverableKeyStoreManager = recoverableKeyStoreManager;
             mUserManagerInternal = userManagerInternal;
             mDeviceStateCache = deviceStateCache;
+            mSettings = settings;
         }
 
         @Override
@@ -119,6 +125,12 @@
         }
 
         @Override
+        public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
+                int defaultValue) {
+            return mSettings.globalGetInt(keyName);
+        }
+
+        @Override
         public UserManagerInternal getUserManagerInternal() {
             return mUserManagerInternal;
         }
@@ -144,27 +156,33 @@
         }
     }
 
+    public MockInjector mInjector;
+
     protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
             LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
             IStorageManager storageManager, IActivityManager mActivityManager,
             SyntheticPasswordManager spManager, IAuthSecret authSecretService,
             FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
-            UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
+            UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
+            FakeSettings settings) {
         super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
                 storageManager, spManager, gsiService,
-                recoverableKeyStoreManager, userManagerInternal, deviceStateCache));
+                recoverableKeyStoreManager, userManagerInternal, deviceStateCache, settings));
         mGateKeeperService = gatekeeper;
         mAuthSecretService = authSecretService;
     }
 
     @Override
-    protected void tieProfileLockToParent(int userId, byte[] password) {
-        mStorage.writeChildProfileLock(userId, password);
+    protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
+        Parcel parcel = Parcel.obtain();
+        parcel.writeParcelable(password, 0);
+        mStorage.writeChildProfileLock(userId, parcel.marshall());
+        parcel.recycle();
     }
 
     @Override
-    protected byte[] getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException,
-            KeyPermanentlyInvalidatedException {
+    protected LockscreenCredential getDecryptedPasswordForTiedProfile(int userId)
+            throws FileNotFoundException, KeyPermanentlyInvalidatedException {
         byte[] storedData = mStorage.readChildProfileLock(userId);
         if (storedData == null) {
             throw new FileNotFoundException("Child profile lock file not found");
@@ -176,6 +194,13 @@
         } catch (RemoteException e) {
             // shouldn't happen.
         }
-        return storedData;
+        Parcel parcel = Parcel.obtain();
+        try {
+            parcel.unmarshall(storedData, 0, storedData.length);
+            parcel.setDataPosition(0);
+            return (LockscreenCredential) parcel.readParcelable(null);
+        } finally {
+            parcel.recycle();
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 5818133..86ef31a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,14 +16,10 @@
 
 package com.android.server.locksettings;
 
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -39,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
 import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
@@ -61,60 +58,51 @@
     }
 
     public void testCreatePasswordPrimaryUser() throws RemoteException {
-        testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD,
-                PASSWORD_QUALITY_ALPHABETIC);
+        testCreateCredential(PRIMARY_USER_ID, newPassword("password"));
     }
 
     public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
-        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password",
-                CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPassword("password"));
     }
 
     public void testCreatePatternPrimaryUser() throws RemoteException {
-        testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
-                PASSWORD_QUALITY_SOMETHING);
+        testCreateCredential(PRIMARY_USER_ID, newPattern("123456789"));
     }
 
     public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
-        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789",
-                CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, newPattern("123456789"));
     }
 
     public void testChangePasswordPrimaryUser() throws RemoteException {
-        testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
-                "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+        testChangeCredentials(PRIMARY_USER_ID, newPattern("78963214"), newPassword("asdfghjk"));
     }
 
     public void testChangePatternPrimaryUser() throws RemoteException {
-        testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD,
-                "1596321", CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+        testChangeCredentials(PRIMARY_USER_ID, newPassword("!£$%^&*(())"), newPattern("1596321"));
     }
 
     public void testChangePasswordFailPrimaryUser() throws RemoteException {
         final long sid = 1234;
-        initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"), sid);
 
-        assertFalse(mService.setLockCredential("newpwd".getBytes(), CREDENTIAL_TYPE_PASSWORD,
-                    "badpwd".getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
-        assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
+        assertFalse(mService.setLockCredential(newPassword("newpwd"), newPassword("badpwd"),
+                    PRIMARY_USER_ID, false));
+        assertVerifyCredentials(PRIMARY_USER_ID, newPassword("password"), sid);
     }
 
     public void testClearPasswordPrimaryUser() throws RemoteException {
-        final String PASSWORD = "password";
-        initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
-        assertTrue(mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD.getBytes(),
-                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false));
-        assertFalse(mService.havePassword(PRIMARY_USER_ID));
-        assertFalse(mService.havePattern(PRIMARY_USER_ID));
+        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"), 1234);
+        assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
+                PRIMARY_USER_ID, false));
+        assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
     public void testManagedProfileUnifiedChallenge() throws RemoteException {
-        final String firstUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-1";
-        final String secondUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-2";
-        assertTrue(mService.setLockCredential(firstUnifiedPassword.getBytes(),
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                null, PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false));
+        final LockscreenCredential firstUnifiedPassword = newPassword("pwd-1");
+        final LockscreenCredential secondUnifiedPassword = newPassword("pwd-2");
+        assertTrue(mService.setLockCredential(firstUnifiedPassword,
+                nonePassword(), PRIMARY_USER_ID, false));
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -132,8 +120,8 @@
         mGateKeeperService.clearAuthToken(TURNED_OFF_PROFILE_USER_ID);
         // verify credential
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                firstUnifiedPassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
-                PRIMARY_USER_ID).getResponseCode());
+                firstUnifiedPassword, 0, PRIMARY_USER_ID)
+                .getResponseCode());
 
         // Verify that we have a new auth token for the profile
         assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
@@ -148,16 +136,15 @@
          */
         mStorageManager.setIgnoreBadUnlock(true);
         // Change primary password and verify that profile SID remains
-        assertTrue(mService.setLockCredential(secondUnifiedPassword.getBytes(),
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, firstUnifiedPassword.getBytes(),
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
+        assertTrue(mService.setLockCredential(
+                secondUnifiedPassword, firstUnifiedPassword, PRIMARY_USER_ID, false));
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
 
         // Clear unified challenge
-        assertTrue(mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
-                secondUnifiedPassword.getBytes(), PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID,
+        assertTrue(mService.setLockCredential(nonePassword(),
+                secondUnifiedPassword, PRIMARY_USER_ID,
                 false));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
@@ -165,19 +152,19 @@
     }
 
     public void testManagedProfileSeparateChallenge() throws RemoteException {
-        final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
-        final String profilePassword = "testManagedProfileSeparateChallenge-profile";
-        assertTrue(mService.setLockCredential(primaryPassword.getBytes(),
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false));
+        final LockscreenCredential primaryPassword = newPassword("primary");
+        final LockscreenCredential profilePassword = newPassword("profile");
+        assertTrue(mService.setLockCredential(primaryPassword,
+                nonePassword(),
+                PRIMARY_USER_ID, false));
         /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
          * credential as part of verifyCredential() before the new credential is committed in
          * StorageManager. So we relax the check in our mock StorageManager to allow that.
          */
         mStorageManager.setIgnoreBadUnlock(true);
-        assertTrue(mService.setLockCredential(profilePassword.getBytes(),
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_COMPLEX, MANAGED_PROFILE_USER_ID, false));
+        assertTrue(mService.setLockCredential(profilePassword,
+                nonePassword(),
+                MANAGED_PROFILE_USER_ID, false));
         mStorageManager.setIgnoreBadUnlock(false);
 
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
@@ -190,81 +177,69 @@
         mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID);
         // verify primary credential
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                primaryPassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
-                PRIMARY_USER_ID).getResponseCode());
+                primaryPassword, 0, PRIMARY_USER_ID)
+                .getResponseCode());
         assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
 
         // verify profile credential
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
-                MANAGED_PROFILE_USER_ID).getResponseCode());
+                profilePassword, 0, MANAGED_PROFILE_USER_ID)
+                .getResponseCode());
         assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
 
         // Change primary credential and make sure we don't affect profile
         mStorageManager.setIgnoreBadUnlock(true);
-        assertTrue(mService.setLockCredential("pwd".getBytes(),
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                primaryPassword.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
+        assertTrue(mService.setLockCredential(
+                newPassword("pwd"), primaryPassword, PRIMARY_USER_ID, false));
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
-                MANAGED_PROFILE_USER_ID).getResponseCode());
+                profilePassword, 0, MANAGED_PROFILE_USER_ID)
+                .getResponseCode());
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
     }
 
     public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
-        final byte[] password = "password".getBytes();
-
         assertTrue(mService.setLockCredential(
-                password,
-                CREDENTIAL_TYPE_PASSWORD,
-                null,
-                PASSWORD_QUALITY_ALPHABETIC,
+                newPassword("password"),
+                nonePassword(),
                 PRIMARY_USER_ID,
                 false));
 
         verify(mRecoverableKeyStoreManager)
-                .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, password, PRIMARY_USER_ID);
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, "password".getBytes(),
+                        PRIMARY_USER_ID);
     }
 
     public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials()
             throws Exception {
-        final byte[] pattern = "12345".getBytes();
-
         assertTrue(mService.setLockCredential(
-                pattern,
-                CREDENTIAL_TYPE_PATTERN,
-                null,
-                PASSWORD_QUALITY_SOMETHING,
+                newPattern("12345"),
+                nonePassword(),
                 MANAGED_PROFILE_USER_ID,
                 false));
 
         verify(mRecoverableKeyStoreManager)
-                .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, "12345".getBytes(),
+                        MANAGED_PROFILE_USER_ID);
     }
 
     public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials()
             throws Exception {
-        final String oldCredential = "12345";
-        final byte[] newCredential = "newPassword".getBytes();
         initializeStorageWithCredential(
                 MANAGED_PROFILE_USER_ID,
-                oldCredential,
-                CREDENTIAL_TYPE_PATTERN,
-                PASSWORD_QUALITY_SOMETHING);
+                newPattern("12345"),
+                1234);
 
         assertTrue(mService.setLockCredential(
-                newCredential,
-                CREDENTIAL_TYPE_PASSWORD,
-                oldCredential.getBytes(),
-                PASSWORD_QUALITY_ALPHABETIC,
+                newPassword("newPassword"),
+                newPattern("12345"),
                 MANAGED_PROFILE_USER_ID,
                 false));
 
         verify(mRecoverableKeyStoreManager)
-                .lockScreenSecretChanged(
-                        CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID);
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, "newPassword".getBytes(),
+                        MANAGED_PROFILE_USER_ID);
     }
 
     public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential()
@@ -272,10 +247,8 @@
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
         assertTrue(mService.setLockCredential(
-                "12345".getBytes(),
-                CREDENTIAL_TYPE_PATTERN,
-                null,
-                PASSWORD_QUALITY_SOMETHING,
+                newPattern("12345"),
+                nonePassword(),
                 PRIMARY_USER_ID,
                 false));
 
@@ -287,40 +260,35 @@
     public void
             testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_updatesBothCredentials()
                     throws Exception {
-        final String oldCredential = "oldPassword";
-        final byte[] newCredential = "newPassword".getBytes();
+        final LockscreenCredential oldCredential = newPassword("oldPassword");
+        final LockscreenCredential newCredential = newPassword("newPassword");
         initializeStorageWithCredential(
-                PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
+                PRIMARY_USER_ID, oldCredential, 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
         assertTrue(mService.setLockCredential(
                 newCredential,
-                CREDENTIAL_TYPE_PASSWORD,
-                oldCredential.getBytes(),
-                PASSWORD_QUALITY_ALPHABETIC,
+                oldCredential,
                 PRIMARY_USER_ID,
                 false));
 
         verify(mRecoverableKeyStoreManager)
-                .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential, PRIMARY_USER_ID);
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential.getCredential(),
+                        PRIMARY_USER_ID);
         verify(mRecoverableKeyStoreManager)
-                .lockScreenSecretChanged(
-                        CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID);
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential.getCredential(),
+                        MANAGED_PROFILE_USER_ID);
     }
 
     public void
             testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials()
                     throws Exception {
-        final String oldCredential = "oldPassword";
-        initializeStorageWithCredential(
-                PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
+        initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("oldPassword"), 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
         assertTrue(mService.setLockCredential(
-                null,
-                CREDENTIAL_TYPE_NONE,
-                oldCredential.getBytes(),
-                PASSWORD_QUALITY_UNSPECIFIED,
+                nonePassword(),
+                newPassword("oldPassword"),
                 PRIMARY_USER_ID,
                 false));
 
@@ -331,17 +299,13 @@
     }
 
     public void testSetLockCredential_nullCredential_removeBiometrics() throws RemoteException {
-        final String oldCredential = "oldPassword";
-
         initializeStorageWithCredential(
                 PRIMARY_USER_ID,
-                oldCredential,
-                CREDENTIAL_TYPE_PATTERN,
-                PASSWORD_QUALITY_SOMETHING);
+                newPattern("123654"),
+                1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
-        mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, oldCredential.getBytes(),
-                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
+        mService.setLockCredential(nonePassword(), newPattern("123654"), PRIMARY_USER_ID, false);
 
         // Verify fingerprint is removed
         verify(mFingerprintManager).remove(any(), eq(PRIMARY_USER_ID), any());
@@ -353,41 +317,33 @@
 
     public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials()
             throws Exception {
-        final String parentPassword = "parentPassword";
-        final byte[] profilePassword = "profilePassword".getBytes();
-        initializeStorageWithCredential(
-                PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234);
+        final LockscreenCredential parentPassword = newPassword("parentPassword");
+        final LockscreenCredential profilePassword = newPassword("profilePassword");
+        initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword, 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
         assertTrue(mService.setLockCredential(
                 profilePassword,
-                CREDENTIAL_TYPE_PASSWORD,
-                null,
-                PASSWORD_QUALITY_ALPHABETIC,
+                nonePassword(),
                 MANAGED_PROFILE_USER_ID,
                 false));
 
         verify(mRecoverableKeyStoreManager)
-                .lockScreenSecretChanged(
-                        CREDENTIAL_TYPE_PASSWORD, profilePassword, MANAGED_PROFILE_USER_ID);
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, profilePassword.getCredential(),
+                        MANAGED_PROFILE_USER_ID);
     }
 
     public void
             testSetLockCredential_forSeparateToUnifiedChallengeProfile_doesNotSendRandomCredential()
                     throws Exception {
-        final String parentPassword = "parentPassword";
-        final String profilePassword = "12345";
-        initializeStorageWithCredential(
-                PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234);
+        final LockscreenCredential parentPassword = newPassword("parentPassword");
+        final LockscreenCredential profilePassword = newPattern("12345");
+        initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword, 1234);
         // Create and verify separate profile credentials.
-        testCreateCredential(
-                MANAGED_PROFILE_USER_ID,
-                profilePassword,
-                CREDENTIAL_TYPE_PATTERN,
-                PASSWORD_QUALITY_SOMETHING);
+        testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
 
         mService.setSeparateProfileChallengeEnabled(
-                MANAGED_PROFILE_USER_ID, false, profilePassword.getBytes());
+                MANAGED_PROFILE_USER_ID, false, profilePassword);
 
         // Called once for setting the initial separate profile credentials and not again during
         // unification.
@@ -396,132 +352,121 @@
     }
 
     public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception {
-        final String password = "password";
-        initializeStorageWithCredential(PRIMARY_USER_ID, password, CREDENTIAL_TYPE_PASSWORD, 1234);
+        final LockscreenCredential password = newPassword("password");
+        initializeStorageWithCredential(PRIMARY_USER_ID, password, 1234);
         reset(mRecoverableKeyStoreManager);
 
-        mService.verifyCredential(
-                password.getBytes(), CREDENTIAL_TYPE_PASSWORD, 1, PRIMARY_USER_ID);
+        mService.verifyCredential(password, 1, PRIMARY_USER_ID);
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretAvailable(
-                        CREDENTIAL_TYPE_PASSWORD, password.getBytes(), PRIMARY_USER_ID);
+                        CREDENTIAL_TYPE_PASSWORD, password.getCredential(), PRIMARY_USER_ID);
     }
 
     public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials()
             throws Exception {
-        final byte[] pattern = "12345".getBytes();
+        final LockscreenCredential pattern = newPattern("12345");
         assertTrue(mService.setLockCredential(
                 pattern,
-                CREDENTIAL_TYPE_PATTERN,
-                null,
-                PASSWORD_QUALITY_SOMETHING,
+                nonePassword(),
                 MANAGED_PROFILE_USER_ID,
                 false));
         reset(mRecoverableKeyStoreManager);
 
-        mService.verifyCredential(pattern, CREDENTIAL_TYPE_PATTERN, 1, MANAGED_PROFILE_USER_ID);
+        mService.verifyCredential(pattern, 1, MANAGED_PROFILE_USER_ID);
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretAvailable(
-                        CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
+                        CREDENTIAL_TYPE_PATTERN, pattern.getCredential(), MANAGED_PROFILE_USER_ID);
     }
 
     public void
             testVerifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth()
                     throws Exception {
-        final String pattern = "12345";
-        initializeStorageWithCredential(PRIMARY_USER_ID, pattern, CREDENTIAL_TYPE_PATTERN, 1234);
+        final LockscreenCredential pattern = newPattern("12345");
+        initializeStorageWithCredential(PRIMARY_USER_ID, pattern, 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         reset(mRecoverableKeyStoreManager);
 
-        mService.verifyCredential(pattern.getBytes(), CREDENTIAL_TYPE_PATTERN, 1, PRIMARY_USER_ID);
+        mService.verifyCredential(pattern, 1, PRIMARY_USER_ID);
 
         // Parent sends its credentials for both the parent and profile.
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretAvailable(
-                        CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), PRIMARY_USER_ID);
+                        CREDENTIAL_TYPE_PATTERN, pattern.getCredential(), PRIMARY_USER_ID);
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretAvailable(
-                        CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), MANAGED_PROFILE_USER_ID);
+                        CREDENTIAL_TYPE_PATTERN, pattern.getCredential(), MANAGED_PROFILE_USER_ID);
         // Profile doesn't send its own random credentials.
         verify(mRecoverableKeyStoreManager, never())
                 .lockScreenSecretAvailable(
                         eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID));
     }
 
-    private void testCreateCredential(int userId, String credential, int type, int quality)
+    private void testCreateCredential(int userId, LockscreenCredential credential)
             throws RemoteException {
-        assertTrue(mService.setLockCredential(credential.getBytes(), type, null, quality,
-                userId, false));
-        assertVerifyCredentials(userId, credential, type, -1);
+        assertTrue(mService.setLockCredential(credential, nonePassword(), userId, false));
+        assertVerifyCredentials(userId, credential, -1);
     }
 
     private void testCreateCredentialFailsWithoutLockScreen(
-            int userId, String credential, int type, int quality) throws RemoteException {
+            int userId, LockscreenCredential credential) throws RemoteException {
         mHasSecureLockScreen = false;
 
         try {
-            mService.setLockCredential(credential.getBytes(), type, null, quality,
-                    userId, false);
+            mService.setLockCredential(credential, null, userId, false);
             fail("An exception should have been thrown.");
         } catch (UnsupportedOperationException e) {
             // Success - the exception was expected.
         }
 
-        assertFalse(mService.havePassword(userId));
-        assertFalse(mService.havePattern(userId));
+        assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(userId));
     }
 
-    private void testChangeCredentials(int userId, String newCredential, int newType,
-            String oldCredential, int oldType, int quality) throws RemoteException {
+    private void testChangeCredentials(int userId, LockscreenCredential newCredential,
+            LockscreenCredential oldCredential) throws RemoteException {
         final long sid = 1234;
-        initializeStorageWithCredential(userId, oldCredential, oldType, sid);
-        assertTrue(mService.setLockCredential(newCredential.getBytes(), newType,
-                oldCredential.getBytes(), quality, userId, false));
-        assertVerifyCredentials(userId, newCredential, newType, sid);
+        initializeStorageWithCredential(userId, oldCredential, sid);
+        assertTrue(mService.setLockCredential(newCredential, oldCredential, userId, false));
+        assertVerifyCredentials(userId, newCredential, sid);
     }
 
-    private void assertVerifyCredentials(int userId, String credential, int type, long sid)
+    private void assertVerifyCredentials(int userId, LockscreenCredential credential, long sid)
             throws RemoteException{
         final long challenge = 54321;
-        VerifyCredentialResponse response = mService.verifyCredential(credential.getBytes(),
-                type, challenge, userId);
+        VerifyCredentialResponse response = mService.verifyCredential(credential,
+                challenge, userId);
 
         assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
         if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
-        final int incorrectType;
-        if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
-            assertTrue(mService.havePassword(userId));
-            assertFalse(mService.havePattern(userId));
-            incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
-        } else if (type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN){
-            assertFalse(mService.havePassword(userId));
-            assertTrue(mService.havePattern(userId));
-            incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+        if (credential.isPassword()) {
+            assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
+        } else if (credential.isPin()) {
+            assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(userId));
+        } else if (credential.isPattern()) {
+            assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(userId));
         } else {
-            assertFalse(mService.havePassword(userId));
-            assertFalse(mService.havePassword(userId));
-            incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+            assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(userId));
         }
-        // check for bad type
-        assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(
-                credential.getBytes(), incorrectType, challenge, userId).getResponseCode());
         // check for bad credential
+        final LockscreenCredential badCredential;
+        if (!credential.isNone()) {
+            badCredential = credential.duplicate();
+            badCredential.getCredential()[0] ^= 1;
+        } else {
+            badCredential = LockscreenCredential.createPin("0");
+        }
         assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(
-                ("0" + credential).getBytes(), type, challenge, userId).getResponseCode());
+                badCredential, challenge, userId).getResponseCode());
     }
 
-    private void initializeStorageWithCredential(int userId, String credential, int type, long sid)
-            throws RemoteException {
-        byte[] credentialBytes = credential == null ? null : credential.getBytes();
-        byte[] oldHash = new VerifyHandle(credential.getBytes(), sid).toBytes();
+    private void initializeStorageWithCredential(int userId, LockscreenCredential credential,
+            long sid) throws RemoteException {
+        byte[] oldHash = new VerifyHandle(credential.getCredential(), sid).toBytes();
         if (mService.shouldMigrateToSyntheticPasswordLocked(userId)) {
-            mService.initializeSyntheticPasswordLocked(oldHash, credentialBytes, type,
-                    type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ? PASSWORD_QUALITY_ALPHABETIC
-                            : PASSWORD_QUALITY_SOMETHING, userId);
+            mService.initializeSyntheticPasswordLocked(oldHash, credential, userId);
         } else {
-            if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
+            if (credential.isPassword() || credential.isPin()) {
                 mStorage.writeCredentialHash(CredentialHash.create(oldHash,
                         LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
             } else {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index b60111e..8c2d172 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -17,11 +17,13 @@
 package com.android.server.locksettings;
 
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 
 import static junit.framework.Assert.assertEquals;
 
-import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.never;
@@ -34,6 +36,8 @@
 import static java.io.FileDescriptor.out;
 
 import android.app.ActivityManager;
+import android.app.admin.PasswordMetrics;
+import android.app.admin.PasswordPolicy;
 import android.content.Context;
 import android.os.Binder;
 import android.os.Handler;
@@ -88,6 +92,7 @@
 
     @Test
     public void testWrongPassword() throws Exception {
+        when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.checkCredential(
@@ -95,18 +100,20 @@
         assertEquals(-1, mCommand.exec(mBinder, in, out, err,
                 new String[] { "set-pin", "--old", "1234" },
                 mShellCallback, mResultReceiver));
-        verify(mLockPatternUtils, never()).setLockCredential(any(), any(),
-                anyInt(), anyBoolean());
+        verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
     }
 
     @Test
     public void testChangePin() throws Exception {
+        when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
                 PASSWORD_QUALITY_NUMERIC);
         when(mLockPatternUtils.checkCredential(
                 LockscreenCredential.createPin("1234"), mUserId, null)).thenReturn(true);
+        when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+                .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_NUMERIC));
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "set-pin", "--old", "1234", "4321" },
                 mShellCallback, mResultReceiver));
@@ -117,6 +124,23 @@
     }
 
     @Test
+    public void testChangePin_nonCompliant() throws Exception {
+        when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
+        when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+        when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+                PASSWORD_QUALITY_NUMERIC);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPin("1234"), mUserId, null)).thenReturn(true);
+        when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+                .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_ALPHABETIC));
+        assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-pin", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
+    }
+
+    @Test
     public void testChangePin_noLockScreen() throws Exception {
         when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
         assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
@@ -128,22 +152,42 @@
 
     @Test
     public void testChangePassword() throws Exception {
+        when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
                 PASSWORD_QUALITY_ALPHABETIC);
         when(mLockPatternUtils.checkCredential(
                 LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(true);
+        when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+                .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_ALPHABETIC));
         assertEquals(0,  mCommand.exec(new Binder(), in, out, err,
-                new String[] { "set-password", "--old", "1234", "4321" },
+                new String[] { "set-password", "--old", "1234", "abcd" },
                 mShellCallback, mResultReceiver));
         verify(mLockPatternUtils).setLockCredential(
-                LockscreenCredential.createPassword("4321"),
+                LockscreenCredential.createPassword("abcd"),
                 LockscreenCredential.createPassword("1234"),
                 mUserId);
     }
 
     @Test
+    public void testChangePassword_nonCompliant() throws Exception {
+        when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
+        when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+        when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+        when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+                PASSWORD_QUALITY_ALPHABETIC);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(true);
+        when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+                .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_COMPLEX));
+        assertEquals(-1,  mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-password", "--old", "1234", "weakpassword" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
+    }
+
+    @Test
     public void testChangePassword_noLockScreen() throws Exception {
         when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
         assertEquals(-1,  mCommand.exec(new Binder(), in, out, err,
@@ -155,11 +199,14 @@
 
     @Test
     public void testChangePattern() throws Exception {
+        when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.checkCredential(
                 LockscreenCredential.createPattern(stringToPattern("1234")),
                 mUserId, null)).thenReturn(true);
+        when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+                .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_SOMETHING));
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "set-pattern", "--old", "1234", "4321" },
                 mShellCallback, mResultReceiver));
@@ -170,6 +217,22 @@
     }
 
     @Test
+    public void testChangePattern_nonCompliant() throws Exception {
+        when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
+        when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+        when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPattern(stringToPattern("1234")),
+                mUserId, null)).thenReturn(true);
+        when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+                .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_NUMERIC));
+        assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-pattern", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
+    }
+
+    @Test
     public void testChangePattern_noLockScreen() throws Exception {
         when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
         assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
@@ -181,11 +244,14 @@
 
     @Test
     public void testClear() throws Exception {
+        when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.checkCredential(
                 LockscreenCredential.createPattern(stringToPattern("1234")),
                 mUserId, null)).thenReturn(true);
+        when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+                .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_UNSPECIFIED));
         assertEquals(0, mCommand.exec(new Binder(), in, out, err,
                 new String[] { "clear", "--old", "1234" },
                 mShellCallback, mResultReceiver));
@@ -195,7 +261,29 @@
                 mUserId);
     }
 
+    @Test
+    public void testClear_nonCompliant() throws Exception {
+        when(mLockPatternUtils.isSecure(mUserId)).thenReturn(true);
+        when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+        when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+        when(mLockPatternUtils.checkCredential(
+                LockscreenCredential.createPattern(stringToPattern("1234")),
+                mUserId, null)).thenReturn(true);
+        when(mLockPatternUtils.getRequestedPasswordMetrics(mUserId))
+                .thenReturn(metricsForAdminQuality(PASSWORD_QUALITY_SOMETHING));
+        assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+                new String[] { "clear", "--old", "1234" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils, never()).setLockCredential(any(), any(), anyInt());
+    }
+
     private List<LockPatternView.Cell> stringToPattern(String str) {
         return LockPatternUtils.byteArrayToPattern(str.getBytes());
     }
+
+    private PasswordMetrics metricsForAdminQuality(int quality) {
+        PasswordPolicy policy = new PasswordPolicy();
+        policy.quality = quality;
+        return policy.getMinMetrics();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index bc61c58..1581d9a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -16,20 +16,51 @@
 
 package com.android.server.locksettings;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
 import android.content.Context;
 
 import com.android.server.PersistentDataBlockManagerInternal;
 
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
 import java.io.File;
+import java.util.Arrays;
 
 public class LockSettingsStorageTestable extends LockSettingsStorage {
 
     public File mStorageDir;
-    public PersistentDataBlockManagerInternal mPersistentDataBlock;
+    public PersistentDataBlockManagerInternal mPersistentDataBlockManager;
+    private byte[] mPersistentData;
 
     public LockSettingsStorageTestable(Context context, File storageDir) {
         super(context);
         mStorageDir = storageDir;
+        mPersistentDataBlockManager = mock(PersistentDataBlockManagerInternal.class);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                byte[] handle = (byte[]) invocation.getArguments()[0];
+                if (handle != null) {
+                    mPersistentData = Arrays.copyOf(handle, handle.length);
+                } else {
+                    mPersistentData = null;
+                }
+                return null;
+            }
+        }).when(mPersistentDataBlockManager).setFrpCredentialHandle(any());
+        // For some reasons, simply mocking getFrpCredentialHandle() with
+        // when(mPersistentDataBlockManager.getFrpCredentialHandle()).thenReturn(mPersistentData)
+        // does not work, I had to use the long-winded way below.
+        doAnswer(new Answer<byte[]>() {
+            @Override
+            public byte[] answer(InvocationOnMock invocation) throws Throwable {
+                return mPersistentData;
+            }
+        }).when(mPersistentDataBlockManager).getFrpCredentialHandle();
     }
 
     @Override
@@ -57,8 +88,8 @@
     }
 
     @Override
-    public PersistentDataBlockManagerInternal getPersistentDataBlock() {
-        return mPersistentDataBlock;
+    PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
+        return mPersistentDataBlockManager;
     }
 
     private File makeDirs(File baseDir, String filePath) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index cb51897..7a18431 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -240,12 +240,12 @@
         writePasswordBytes(PASSWORD_0, 10);
         writePatternBytes(PATTERN_0, 20);
 
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN,
                 mStorage.readCredentialHash(10).type);
         assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
                 mStorage.readCredentialHash(20).type);
         mStorage.clearCache();
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN,
                 mStorage.readCredentialHash(10).type);
         assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
                 mStorage.readCredentialHash(20).type);
@@ -352,20 +352,20 @@
     }
 
     public void testPersistentDataBlock_unavailable() {
-        mStorage.mPersistentDataBlock = null;
+        mStorage.mPersistentDataBlockManager = null;
 
         assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
     }
 
     public void testPersistentDataBlock_empty() {
-        mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
+        mStorage.mPersistentDataBlockManager = mock(PersistentDataBlockManagerInternal.class);
 
         assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
     }
 
     public void testPersistentDataBlock_withData() {
-        mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
-        when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
+        mStorage.mPersistentDataBlockManager = mock(PersistentDataBlockManagerInternal.class);
+        when(mStorage.mPersistentDataBlockManager.getFrpCredentialHandle())
                 .thenReturn(PersistentData.toBytes(PersistentData.TYPE_SP_WEAVER, SOME_USER_ID,
                         DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD));
 
@@ -378,8 +378,8 @@
     }
 
     public void testPersistentDataBlock_exception() {
-        mStorage.mPersistentDataBlock = mock(PersistentDataBlockManagerInternal.class);
-        when(mStorage.mPersistentDataBlock.getFrpCredentialHandle())
+        mStorage.mPersistentDataBlockManager = mock(PersistentDataBlockManagerInternal.class);
+        when(mStorage.mPersistentDataBlockManager.getFrpCredentialHandle())
                 .thenThrow(new IllegalStateException("oops"));
         assertSame(PersistentData.NONE, mStorage.readPersistentDataBlock());
     }
@@ -453,7 +453,7 @@
 
     private void assertPasswordBytes(byte[] password, int userId) {
         CredentialHash cred = mStorage.readCredentialHash(userId);
-        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, cred.type);
+        assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN, cred.type);
         assertArrayEquals(password, cred.hash);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
new file mode 100644
index 0000000..df719b6d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.locksettings;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
+import static com.android.internal.widget.LockPatternUtils.USER_FRP;
+
+import android.app.admin.DevicePolicyManager;
+
+import com.android.internal.widget.VerifyCredentialResponse;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+
+
+/** Test setting a lockscreen credential and then verify it under USER_FRP */
+public class LockscreenFrpTest extends BaseLockSettingsServiceTests {
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        // FRP credential can only be verified prior to provisioning
+        mSettings.setDeviceProvisioned(false);
+    }
+
+    public void testFrpCredential_setPin() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID, false);
+
+        assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(USER_FRP));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+    }
+
+    public void testFrpCredential_setPattern() {
+        mService.setLockCredential(newPattern("4321"), nonePassword(), PRIMARY_USER_ID, false);
+
+        assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(USER_FRP));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPattern("4321"), 0, USER_FRP).getResponseCode());
+    }
+
+    public void testFrpCredential_setPassword() {
+        mService.setLockCredential(newPassword("4321"), nonePassword(), PRIMARY_USER_ID, false);
+
+        assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPassword("4321"), 0, USER_FRP).getResponseCode());
+    }
+
+    public void testFrpCredential_changeCredential() {
+        mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID, false);
+        mService.setLockCredential(newPattern("5678"), newPassword("1234"), PRIMARY_USER_ID, false);
+
+        assertEquals(CREDENTIAL_TYPE_PATTERN, mService.getCredentialType(USER_FRP));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPattern("5678"), 0, USER_FRP).getResponseCode());
+    }
+
+    public void testFrpCredential_removeCredential() {
+        mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID, false);
+        assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
+
+        mService.setLockCredential(nonePassword(), newPassword("1234"), PRIMARY_USER_ID, false);
+        assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(USER_FRP));
+    }
+
+    public void testFrpCredential_cannotVerifyAfterProvsioning() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID, false);
+
+        mSettings.setDeviceProvisioned(true);
+        assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
+                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+    }
+
+    public void testFrpCredential_legacyPinTypePersistentData() {
+        mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID, false);
+        PersistentData data = mStorage.readPersistentDataBlock();
+        // Tweak the existing persistent data to make it look like one with legacy credential type
+        assertEquals(CREDENTIAL_TYPE_PIN, data.payload[3]);
+        data.payload[3] = CREDENTIAL_TYPE_PASSWORD_OR_PIN;
+        mStorage.writePersistentDataBlock(data.type, data.userId,
+                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, data.payload);
+
+        assertEquals(CREDENTIAL_TYPE_PIN, mService.getCredentialType(USER_FRP));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+
+    }
+
+    public void testFrpCredential_legacyPasswordTypePersistentData() {
+        mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID, false);
+        PersistentData data = mStorage.readPersistentDataBlock();
+        // Tweak the existing persistent data to make it look like one with legacy credential type
+        assertEquals(CREDENTIAL_TYPE_PASSWORD, data.payload[3]);
+        data.payload[3] = CREDENTIAL_TYPE_PASSWORD_OR_PIN;
+        mStorage.writePersistentDataBlock(data.type, data.userId,
+                DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, data.payload);
+
+        assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK,
+                mService.verifyCredential(newPin("1234"), 0, USER_FRP).getResponseCode());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 42ca42a..89a279c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -16,11 +16,9 @@
 
 package com.android.server.locksettings;
 
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
 
@@ -38,7 +36,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
@@ -72,15 +69,14 @@
 
     public void testPasswordBasedSyntheticPassword() throws RemoteException {
         final int USER_ID = 10;
-        final byte[] password = "user-password".getBytes();
-        final byte[] badPassword = "bad-password".getBytes();
+        final LockscreenCredential password = newPassword("user-password");
+        final LockscreenCredential badPassword = newPassword("bad-password");
         MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
                 mGateKeeperService, mUserManager, mPasswordSlotManager);
         AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
                 null, USER_ID);
         long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService,
-                password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, authToken,
-                PASSWORD_QUALITY_ALPHABETIC, USER_ID);
+                password, authToken, USER_ID);
 
         AuthenticationResult result = manager.unwrapPasswordBasedSyntheticPassword(
                 mGateKeeperService, handle, password, USER_ID, null);
@@ -105,97 +101,90 @@
     }
 
     public void testPasswordMigration() throws RemoteException {
-        final byte[] password = "testPasswordMigration-password".getBytes();
+        final LockscreenCredential password = newPassword("testPasswordMigration-password");
 
         disableSyntheticPassword();
-        mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+        assertTrue(mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID, false));
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
         enableSyntheticPassword();
         // Performs migration
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                password, 0, PRIMARY_USER_ID)
                     .getResponseCode());
         assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
 
         // SP-based verification
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password,
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, 0, PRIMARY_USER_ID)
                         .getResponseCode());
         assertArrayNotEquals(primaryStorageKey,
                 mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
-    protected void initializeCredentialUnderSP(byte[] password, int userId) throws RemoteException {
+    protected void initializeCredentialUnderSP(LockscreenCredential password, int userId)
+            throws RemoteException {
         enableSyntheticPassword();
-        int quality = password != null ? PASSWORD_QUALITY_ALPHABETIC
-                : PASSWORD_QUALITY_UNSPECIFIED;
-        int type = password != null ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
-                : LockPatternUtils.CREDENTIAL_TYPE_NONE;
-        mService.setLockCredential(password, type, null, quality, userId, false);
+        mService.setLockCredential(password, nonePassword(), userId, false);
+        assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
+        assertTrue(mService.isSyntheticPasswordBasedCredential(userId));
     }
 
     public void testSyntheticPasswordChangeCredential() throws RemoteException {
-        final byte[] password = "testSyntheticPasswordChangeCredential-password".getBytes();
-        final byte[] newPassword = "testSyntheticPasswordChangeCredential-newpassword".getBytes();
+        final LockscreenCredential password = newPassword("password");
+        final LockscreenCredential newPassword = newPassword("newpassword");
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
-        mService.setLockCredential(newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, password,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+        mService.setLockCredential(newPassword, password, PRIMARY_USER_ID, false);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                newPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
         assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
     public void testSyntheticPasswordVerifyCredential() throws RemoteException {
-        final byte[] password = "testSyntheticPasswordVerifyCredential-password".getBytes();
-        final byte[] badPassword = "testSyntheticPasswordVerifyCredential-badpassword".getBytes();
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential badPassword = newPassword("badpassword");
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                password, 0, PRIMARY_USER_ID)
                         .getResponseCode());
 
         assertEquals(VerifyCredentialResponse.RESPONSE_ERROR, mService.verifyCredential(
-                badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                badPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
     }
 
     public void testSyntheticPasswordClearCredential() throws RemoteException {
-        final byte[] password = "testSyntheticPasswordClearCredential-password".getBytes();
-        final byte[] badPassword = "testSyntheticPasswordClearCredential-newpassword".getBytes();
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential badPassword = newPassword("newpassword");
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         // clear password
-        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, password,
-                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
+        mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID, false);
         assertEquals(0 ,mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
 
         // set a new password
-        mService.setLockCredential(badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+        mService.setLockCredential(badPassword, nonePassword(),
+                PRIMARY_USER_ID, false);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                badPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
         assertNotEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
     }
 
     public void testSyntheticPasswordChangeCredentialKeepsAuthSecret() throws RemoteException {
-        final byte[] password =
-                "testSyntheticPasswordChangeCredentialKeepsAuthSecret-password".getBytes();
-        final byte[] badPassword =
-                "testSyntheticPasswordChangeCredentialKeepsAuthSecret-new".getBytes();
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential badPassword = newPassword("new");
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
-        mService.setLockCredential(badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, password,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+        mService.setLockCredential(badPassword, password, PRIMARY_USER_ID, false);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                badPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                badPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
 
         // Check the same secret was passed each time
@@ -205,41 +194,35 @@
     }
 
     public void testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
-        final byte[] password =
-                "testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret-password".getBytes();
-        final byte[] newPassword =
-                "testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret-new".getBytes();
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential newPassword = newPassword("new");
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         reset(mAuthSecretService);
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(password,
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, 0, PRIMARY_USER_ID)
                         .getResponseCode());
         verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
     }
 
     public void testSecondaryUserDoesNotPassAuthSecret() throws RemoteException {
-        final byte[] password = "testSecondaryUserDoesNotPassAuthSecret-password".getBytes();
+        LockscreenCredential password = newPassword("password");
 
         initializeCredentialUnderSP(password, SECONDARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, SECONDARY_USER_ID)
+                password, 0, SECONDARY_USER_ID)
                         .getResponseCode());
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
     }
 
     public void testNoSyntheticPasswordOrCredentialDoesNotPassAuthSecret() throws RemoteException {
-        // Setting null doesn't create a synthetic password
-        initializeCredentialUnderSP(null, PRIMARY_USER_ID);
-
-        reset(mAuthSecretService);
         mService.onUnlockUser(PRIMARY_USER_ID);
         flushHandlerTasks();
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
     }
 
     public void testSyntheticPasswordAndCredentialDoesNotPassAuthSecret() throws RemoteException {
-        final byte[] password = "passwordForASyntheticPassword".getBytes();
+        LockscreenCredential password = newPassword("passwordForASyntheticPassword");
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
 
         reset(mAuthSecretService);
@@ -249,10 +232,9 @@
     }
 
     public void testSyntheticPasswordButNoCredentialPassesAuthSecret() throws RemoteException {
-        final byte[] password = "getASyntheticPassword".getBytes();
+        LockscreenCredential password = newPassword("getASyntheticPassword");
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
-        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, password,
-                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
+        mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID, false);
 
         reset(mAuthSecretService);
         mService.onUnlockUser(PRIMARY_USER_ID);
@@ -261,15 +243,14 @@
     }
 
     public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
-        final byte[] UnifiedPassword = "testManagedProfileUnifiedChallengeMigration-pwd".getBytes();
+        LockscreenCredential UnifiedPassword = newPassword("unified-pwd");
         disableSyntheticPassword();
-        mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+        mService.setLockCredential(UnifiedPassword, nonePassword(), PRIMARY_USER_ID, false);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
-        final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
-        final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+        byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+        byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
         assertTrue(primarySid != 0);
         assertTrue(profileSid != 0);
         assertTrue(profileSid != primarySid);
@@ -277,12 +258,12 @@
         // do migration
         enableSyntheticPassword();
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                UnifiedPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
 
         // verify
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                UnifiedPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
         assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
@@ -295,19 +276,16 @@
     }
 
     public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
-        final byte[] primaryPassword =
-                "testManagedProfileSeparateChallengeMigration-primary".getBytes();
-        final byte[] profilePassword =
-                "testManagedProfileSeparateChallengeMigration-profile".getBytes();
+        LockscreenCredential primaryPassword = newPassword("primary");
+        LockscreenCredential profilePassword = newPassword("profile");
         disableSyntheticPassword();
-        mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
-        mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, MANAGED_PROFILE_USER_ID, false);
+        mService.setLockCredential(primaryPassword, nonePassword(), PRIMARY_USER_ID, false);
+        mService.setLockCredential(profilePassword, nonePassword(),
+                MANAGED_PROFILE_USER_ID, false);
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
-        final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
-        final byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
+        byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+        byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
         assertTrue(primarySid != 0);
         assertTrue(profileSid != 0);
         assertTrue(profileSid != primarySid);
@@ -315,19 +293,19 @@
         // do migration
         enableSyntheticPassword();
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                primaryPassword, 0, PRIMARY_USER_ID)
                         .getResponseCode());
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                0, MANAGED_PROFILE_USER_ID).getResponseCode());
+                profilePassword, 0, MANAGED_PROFILE_USER_ID)
+                .getResponseCode());
 
         // verify
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
+                primaryPassword, 0, PRIMARY_USER_ID)
+                .getResponseCode());
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                0, MANAGED_PROFILE_USER_ID).getResponseCode());
+                profilePassword, 0, MANAGED_PROFILE_USER_ID)
+                .getResponseCode());
         assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertArrayNotEquals(primaryStorageKey,
@@ -339,101 +317,92 @@
     }
 
     public void testTokenBasedResetPassword() throws RemoteException {
-        final byte[] password = "password".getBytes();
-        final byte[] pattern = "123654".getBytes();
-        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential pattern = newPattern("123654");
+        byte[] token = "some-high-entropy-secure-token".getBytes();
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         // Disregard any reportPasswordChanged() invocations as part of credential setup.
         flushHandlerTasks();
         reset(mDevicePolicyManager);
 
-        final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+        byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertTrue(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
-                PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
 
-        mLocalService.setLockCredentialWithToken(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
-                handle, token, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
+        mLocalService.setLockCredentialWithToken(pattern, handle, token, PRIMARY_USER_ID);
 
         // Verify DPM gets notified about new device lock
         flushHandlerTasks();
-        final PasswordMetrics metric = PasswordMetrics.computeForCredential(
-                LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(pattern)));
+        final PasswordMetrics metric = PasswordMetrics.computeForCredential(pattern);
         assertEquals(metric, mService.getUserPasswordMetrics(PRIMARY_USER_ID));
         verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)
+                pattern, 0, PRIMARY_USER_ID)
                     .getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
     public void testTokenBasedClearPassword() throws RemoteException {
-        final byte[] password = "password".getBytes();
-        final byte[] pattern = "123654".getBytes();
-        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential pattern = newPattern("123654");
+        byte[] token = "some-high-entropy-secure-token".getBytes();
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
-        final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+        byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mLocalService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
-                handle, token, PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID);
+        mLocalService.setLockCredentialWithToken(nonePassword(), handle, token, PRIMARY_USER_ID);
         flushHandlerTasks(); // flush the unlockUser() call before changing password again
-        mLocalService.setLockCredentialWithToken(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
-                handle, token, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID);
+        mLocalService.setLockCredentialWithToken(pattern, handle, token,
+                PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID)
+                pattern, 0, PRIMARY_USER_ID)
                         .getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
     public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException {
-        final byte[] password = "password".getBytes();
-        final byte[] pattern = "123654".getBytes();
-        final byte[] newPassword = "password".getBytes();
-        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential pattern = newPattern("123654");
+        LockscreenCredential newPassword = newPassword("password");
+        byte[] token = "some-high-entropy-secure-token".getBytes();
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
-        final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
+        byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.verifyCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                0, PRIMARY_USER_ID).getResponseCode();
+        mService.verifyCredential(password, 0, PRIMARY_USER_ID).getResponseCode();
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
-        mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, password,
-                PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID, false);
+        mService.setLockCredential(pattern, password, PRIMARY_USER_ID, false);
 
-        mLocalService.setLockCredentialWithToken(newPassword,
-                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+        mLocalService.setLockCredentialWithToken(newPassword, handle, token, PRIMARY_USER_ID);
 
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                newPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                newPassword, 0, PRIMARY_USER_ID)
                     .getResponseCode());
         assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
     }
 
     public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()
             throws RemoteException {
-        final String token = "some-high-entropy-secure-token";
+        final byte[] token = "some-high-entropy-secure-token".getBytes();
         enableSyntheticPassword();
-        long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID, null);
+        long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
@@ -441,9 +410,15 @@
 
     public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration()
             throws RemoteException {
-        final String token = "some-high-entropy-secure-token";
-        initializeCredentialUnderSP(null, PRIMARY_USER_ID);
-        long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID, null);
+        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        // By first setting a password and then clearing it, we enter the state where SP is
+        // initialized but the user currently has no password
+        initializeCredentialUnderSP(newPassword("password"), PRIMARY_USER_ID);
+        assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
+                PRIMARY_USER_ID, false));
+        assertTrue(mService.isSyntheticPasswordBasedCredential(PRIMARY_USER_ID));
+
+        long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
@@ -451,12 +426,11 @@
 
     public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration()
             throws RemoteException {
-        final byte[] token = "some-high-entropy-secure-token".getBytes();
-        final byte[] password = "password".getBytes();
+        byte[] token = "some-high-entropy-secure-token".getBytes();
+        LockscreenCredential password = newPassword("password");
         // Set up pre-SP user password
         disableSyntheticPassword();
-        mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID, false);
         enableSyntheticPassword();
 
         long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -464,14 +438,14 @@
         assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
         // Activate token (password gets migrated to SP at the same time)
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                password, 0, PRIMARY_USER_ID)
                     .getResponseCode());
         // Verify token is activated
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
 
     public void testEscrowTokenCannotBeActivatedOnUnmanagedUser() {
-        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        byte[] token = "some-high-entropy-secure-token".getBytes();
         when(mUserManagerInternal.isDeviceManaged()).thenReturn(false);
         when(mUserManagerInternal.isUserManaged(PRIMARY_USER_ID)).thenReturn(false);
         when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
@@ -483,9 +457,9 @@
     }
 
     public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
-        final byte[] password = "password".getBytes();
-        final byte[] pattern = "123654".getBytes();
-        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential pattern = newPattern("123654");
+        byte[] token = "some-high-entropy-secure-token".getBytes();
 
         mHasSecureLockScreen = false;
         enableSyntheticPassword();
@@ -493,57 +467,50 @@
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
 
         try {
-            mLocalService.setLockCredentialWithToken(password,
-                    LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token,
-                    PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+            mLocalService.setLockCredentialWithToken(password, handle, token, PRIMARY_USER_ID);
             fail("An exception should have been thrown.");
         } catch (UnsupportedOperationException e) {
             // Success - the exception was expected.
         }
-        assertFalse(mService.havePassword(PRIMARY_USER_ID));
+        assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
 
         try {
-            mLocalService.setLockCredentialWithToken(pattern,
-                    LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token,
-                    PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+            mLocalService.setLockCredentialWithToken(pattern, handle, token, PRIMARY_USER_ID);
             fail("An exception should have been thrown.");
         } catch (UnsupportedOperationException e) {
             // Success - the exception was expected.
         }
-        assertFalse(mService.havePattern(PRIMARY_USER_ID));
+        assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
     }
 
     public void testGetHashFactorPrimaryUser() throws RemoteException {
-        final byte[] password = "password".getBytes();
-        mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
-        final byte[] hashFactor = mService.getHashFactor(password, PRIMARY_USER_ID);
+        LockscreenCredential password = newPassword("password");
+        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID, false);
+        byte[] hashFactor = mService.getHashFactor(password, PRIMARY_USER_ID);
         assertNotNull(hashFactor);
 
-        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
-                password, PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
-        final byte[] newHashFactor = mService.getHashFactor(null, PRIMARY_USER_ID);
+        mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID, false);
+        byte[] newHashFactor = mService.getHashFactor(nonePassword(), PRIMARY_USER_ID);
         assertNotNull(newHashFactor);
         // Hash factor should never change after password change/removal
         assertArrayEquals(hashFactor, newHashFactor);
     }
 
     public void testGetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
-        final byte[] pattern = "1236".getBytes();
-        mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
-                null, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID, false);
+        LockscreenCredential pattern = newPattern("1236");
+        mService.setLockCredential(pattern, nonePassword(), PRIMARY_USER_ID, false);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         assertNotNull(mService.getHashFactor(null, MANAGED_PROFILE_USER_ID));
     }
 
     public void testGetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
-        final byte[] primaryPassword = "primary".getBytes();
-        final byte[] profilePassword = "profile".getBytes();
-        mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
-        mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_ALPHABETIC, MANAGED_PROFILE_USER_ID, false);
-        assertNotNull(mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
+        LockscreenCredential primaryPassword = newPassword("primary");
+        LockscreenCredential profilePassword = newPassword("profile");
+        mService.setLockCredential(primaryPassword, nonePassword(), PRIMARY_USER_ID, false);
+        mService.setLockCredential(profilePassword, nonePassword(),
+                MANAGED_PROFILE_USER_ID, false);
+        assertNotNull(
+                mService.getHashFactor(profilePassword, MANAGED_PROFILE_USER_ID));
     }
 
     public void testPasswordData_serializeDeserialize() {
@@ -551,7 +518,7 @@
         data.scryptN = 11;
         data.scryptR = 22;
         data.scryptP = 33;
-        data.passwordType = CREDENTIAL_TYPE_PASSWORD;
+        data.credentialType = CREDENTIAL_TYPE_PASSWORD;
         data.salt = PAYLOAD;
         data.passwordHandle = PAYLOAD2;
 
@@ -560,7 +527,7 @@
         assertEquals(11, deserialized.scryptN);
         assertEquals(22, deserialized.scryptR);
         assertEquals(33, deserialized.scryptP);
-        assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+        assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.credentialType);
         assertArrayEquals(PAYLOAD, deserialized.salt);
         assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
     }
@@ -569,7 +536,7 @@
         // Test that we can deserialize existing PasswordData and don't inadvertently change the
         // wire format.
         byte[] serialized = new byte[] {
-                0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD */
+                0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD_OR_PIN */
                 11, /* scryptN */
                 22, /* scryptR */
                 33, /* scryptP */
@@ -583,7 +550,7 @@
         assertEquals(11, deserialized.scryptN);
         assertEquals(22, deserialized.scryptR);
         assertEquals(33, deserialized.scryptP);
-        assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.passwordType);
+        assertEquals(CREDENTIAL_TYPE_PASSWORD_OR_PIN, deserialized.credentialType);
         assertArrayEquals(PAYLOAD, deserialized.salt);
         assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
     }
@@ -591,11 +558,11 @@
     public void testGsiDisablesAuthSecret() throws RemoteException {
         mGsiService.setIsGsiRunning(true);
 
-        final byte[] password = "testGsiDisablesAuthSecret-password".getBytes();
+        LockscreenCredential password = newPassword("testGsiDisablesAuthSecret-password");
 
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID)
+                password, 0, PRIMARY_USER_ID)
                         .getResponseCode());
         verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index a992dd1..7d3ec03 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -23,6 +23,7 @@
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -31,7 +32,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -162,21 +162,6 @@
     }
 
     @Test
-    public void isPin_isTrueForNumericString() {
-        assertTrue(KeySyncTask.isPin("3298432574398654376547".getBytes()));
-    }
-
-    @Test
-    public void isPin_isFalseForStringContainingLetters() {
-        assertFalse(KeySyncTask.isPin("398i54369548654".getBytes()));
-    }
-
-    @Test
-    public void isPin_isFalseForStringContainingSymbols() {
-        assertFalse(KeySyncTask.isPin("-3987543643".getBytes()));
-    }
-
-    @Test
     public void hashCredentialsBySaltedSha256_returnsSameHashForSameCredentialsAndSalt() {
         String credentials = "password1234";
         byte[] salt = randomBytes(16);
@@ -221,19 +206,19 @@
     @Test
     public void getUiFormat_returnsPinIfPin() {
         assertEquals(UI_FORMAT_PIN,
-                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234".getBytes()));
+                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PIN));
     }
 
     @Test
     public void getUiFormat_returnsPasswordIfPassword() {
         assertEquals(UI_FORMAT_PASSWORD,
-                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a".getBytes()));
+                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD));
     }
 
     @Test
     public void getUiFormat_returnsPatternIfPattern() {
         assertEquals(UI_FORMAT_PATTERN,
-                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234".getBytes()));
+                KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN));
 
     }
 
@@ -683,7 +668,7 @@
                 mRecoverySnapshotStorage,
                 mSnapshotListenersStorage,
                 TEST_USER_ID,
-                CREDENTIAL_TYPE_PASSWORD,
+                CREDENTIAL_TYPE_PIN,
                 /*credential=*/ pin.getBytes(),
                 /*credentialUpdated=*/ false,
                 mPlatformKeyManager,
@@ -799,7 +784,7 @@
           mRecoverySnapshotStorage,
           mSnapshotListenersStorage,
           TEST_USER_ID,
-          /*credentialType=*/ 3,
+          /*credentialType=*/ 5, // Some invalid credential type value
           "12345".getBytes(),
           /*credentialUpdated=*/ false,
           mPlatformKeyManager,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
index 9e00077..d797955 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeZoneDetectorStrategyTest.java
@@ -23,7 +23,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Intent;
 import android.icu.util.Calendar;
 import android.icu.util.GregorianCalendar;
@@ -45,6 +45,8 @@
             .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
             .build();
 
+    private static final int ARBITRARY_PHONE_ID = 123456;
+
     private Script mScript;
 
     @Before
@@ -53,30 +55,32 @@
     }
 
     @Test
-    public void testSuggestTime_nitz_timeDetectionEnabled() {
+    public void testSuggestPhoneTime_nitz_timeDetectionEnabled() {
         Scenario scenario = SCENARIO_1;
         mScript.pokeFakeClocks(scenario)
                 .pokeTimeDetectionEnabled(true);
 
-        TimeSignal timeSignal = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
+        PhoneTimeSuggestion timeSuggestion =
+                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
         final int clockIncrement = 1000;
         long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement;
 
         mScript.simulateTimePassing(clockIncrement)
-                .simulateTimeSignalReceived(timeSignal)
+                .simulatePhoneTimeSuggestion(timeSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis);
     }
 
     @Test
-    public void testSuggestTime_systemClockThreshold() {
+    public void testSuggestPhoneTime_systemClockThreshold() {
         Scenario scenario = SCENARIO_1;
         final int systemClockUpdateThresholdMillis = 1000;
         mScript.pokeFakeClocks(scenario)
                 .pokeThresholds(systemClockUpdateThresholdMillis)
                 .pokeTimeDetectionEnabled(true);
 
-        TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
-        TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
+        PhoneTimeSuggestion timeSuggestion1 =
+                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
 
         final int clockIncrement = 100;
         // Increment the the device clocks to simulate the passage of time.
@@ -86,7 +90,7 @@
                 TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
 
         // Send the first time signal. It should be used.
-        mScript.simulateTimeSignalReceived(timeSignal1)
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
                 .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis1);
 
         // Now send another time signal, but one that is too similar to the last one and should be
@@ -95,9 +99,9 @@
         TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
                 mScript.peekElapsedRealtimeMillis(),
                 mScript.peekSystemClockMillis() + underThresholdMillis);
-        TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
+        PhoneTimeSuggestion timeSuggestion2 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
         mScript.simulateTimePassing(clockIncrement)
-                .simulateTimeSignalReceived(timeSignal2)
+                .simulatePhoneTimeSuggestion(timeSuggestion2)
                 .verifySystemClockWasNotSetAndResetCallTracking();
 
         // Now send another time signal, but one that is on the threshold and so should be used.
@@ -105,42 +109,44 @@
                 mScript.peekElapsedRealtimeMillis(),
                 mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
 
-        TimeSignal timeSignal3 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime3);
+        PhoneTimeSuggestion timeSuggestion3 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
         mScript.simulateTimePassing(clockIncrement);
 
         long expectSystemClockMillis3 =
                 TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis());
 
-        mScript.simulateTimeSignalReceived(timeSignal3)
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
                 .verifySystemClockWasSetAndResetCallTracking(expectSystemClockMillis3);
     }
 
     @Test
-    public void testSuggestTime_nitz_timeDetectionDisabled() {
+    public void testSuggestPhoneTime_nitz_timeDetectionDisabled() {
         Scenario scenario = SCENARIO_1;
         mScript.pokeFakeClocks(scenario)
                 .pokeTimeDetectionEnabled(false);
 
-        TimeSignal timeSignal = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
-        mScript.simulateTimeSignalReceived(timeSignal)
+        PhoneTimeSuggestion timeSuggestion =
+                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion)
                 .verifySystemClockWasNotSetAndResetCallTracking();
     }
 
     @Test
-    public void testSuggestTime_nitz_invalidNitzReferenceTimesIgnored() {
+    public void testSuggestPhoneTime_nitz_invalidNitzReferenceTimesIgnored() {
         Scenario scenario = SCENARIO_1;
         final int systemClockUpdateThreshold = 2000;
         mScript.pokeFakeClocks(scenario)
                 .pokeThresholds(systemClockUpdateThreshold)
                 .pokeTimeDetectionEnabled(true);
-        TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
-        TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
+        PhoneTimeSuggestion timeSuggestion1 =
+                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
 
         // Initialize the strategy / device with a time set from NITZ.
         mScript.simulateTimePassing(100);
         long expectedSystemClockMillis1 =
                 TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
-        mScript.simulateTimeSignalReceived(timeSignal1)
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1);
 
         // The UTC time increment should be larger than the system clock update threshold so we
@@ -152,8 +158,8 @@
         long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1;
         TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
                 referenceTimeBeforeLastSignalMillis, validUtcTimeMillis);
-        TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
-        mScript.simulateTimeSignalReceived(timeSignal2)
+        PhoneTimeSuggestion timeSuggestion2 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
                 .verifySystemClockWasNotSetAndResetCallTracking();
 
         // Now supply a new signal that has an obviously bogus reference time : substantially in the
@@ -162,8 +168,8 @@
                 utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1;
         TimestampedValue<Long> utcTime3 = new TimestampedValue<>(
                 referenceTimeInFutureMillis, validUtcTimeMillis);
-        TimeSignal timeSignal3 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime3);
-        mScript.simulateTimeSignalReceived(timeSignal3)
+        PhoneTimeSuggestion timeSuggestion3 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3);
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
                 .verifySystemClockWasNotSetAndResetCallTracking();
 
         // Just to prove validUtcTimeMillis is valid.
@@ -172,13 +178,13 @@
                 validReferenceTimeMillis, validUtcTimeMillis);
         long expectedSystemClockMillis4 =
                 TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
-        TimeSignal timeSignal4 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime4);
-        mScript.simulateTimeSignalReceived(timeSignal4)
+        PhoneTimeSuggestion timeSuggestion4 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime4);
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4);
     }
 
     @Test
-    public void testSuggestTime_timeDetectionToggled() {
+    public void testSuggestPhoneTime_timeDetectionToggled() {
         Scenario scenario = SCENARIO_1;
         final int clockIncrementMillis = 100;
         final int systemClockUpdateThreshold = 2000;
@@ -186,15 +192,16 @@
                 .pokeThresholds(systemClockUpdateThreshold)
                 .pokeTimeDetectionEnabled(false);
 
-        TimeSignal timeSignal1 = scenario.createTimeSignalForActual(TimeSignal.SOURCE_ID_NITZ);
-        TimestampedValue<Long> utcTime1 = timeSignal1.getUtcTime();
+        PhoneTimeSuggestion timeSuggestion1 =
+                scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID);
+        TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
 
         // Simulate time passing.
         mScript.simulateTimePassing(clockIncrementMillis);
 
         // Simulate the time signal being received. It should not be used because auto time
         // detection is off but it should be recorded.
-        mScript.simulateTimeSignalReceived(timeSignal1)
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
                 .verifySystemClockWasNotSetAndResetCallTracking();
 
         // Simulate more time passing.
@@ -216,7 +223,7 @@
         TimestampedValue<Long> utcTime2 = new TimestampedValue<>(
                 mScript.peekElapsedRealtimeMillis(),
                 mScript.peekSystemClockMillis() + systemClockUpdateThreshold);
-        TimeSignal timeSignal2 = new TimeSignal(TimeSignal.SOURCE_ID_NITZ, utcTime2);
+        PhoneTimeSuggestion timeSuggestion2 = new PhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2);
 
         // Simulate more time passing.
         mScript.simulateTimePassing(clockIncrementMillis);
@@ -226,7 +233,7 @@
 
         // The new time, though valid, should not be set in the system clock because auto time is
         // disabled.
-        mScript.simulateTimeSignalReceived(timeSignal2)
+        mScript.simulatePhoneTimeSuggestion(timeSuggestion2)
                 .verifySystemClockWasNotSetAndResetCallTracking();
 
         // Turn on auto time detection.
@@ -234,17 +241,6 @@
                 .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2);
     }
 
-    @Test
-    public void testSuggestTime_unknownSource() {
-        Scenario scenario = SCENARIO_1;
-        mScript.pokeFakeClocks(scenario)
-                .pokeTimeDetectionEnabled(true);
-
-        TimeSignal timeSignal = scenario.createTimeSignalForActual("unknown");
-        mScript.simulateTimeSignalReceived(timeSignal)
-                .verifySystemClockWasNotSetAndResetCallTracking();
-    }
-
     /**
      * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
      * like the real thing should, it also asserts preconditions.
@@ -407,8 +403,8 @@
             return mFakeCallback.peekSystemClockMillis();
         }
 
-        Script simulateTimeSignalReceived(TimeSignal timeSignal) {
-            mSimpleTimeDetectorStrategy.suggestTime(timeSignal);
+        Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) {
+            mSimpleTimeDetectorStrategy.suggestPhoneTime(timeSuggestion);
             return this;
         }
 
@@ -466,10 +462,10 @@
             return mActualTimeMillis;
         }
 
-        TimeSignal createTimeSignalForActual(String sourceId) {
+        PhoneTimeSuggestion createPhoneTimeSuggestionForActual(int phoneId) {
             TimestampedValue<Long> time = new TimestampedValue<>(
                     mInitialDeviceRealtimeMillis, mActualTimeMillis);
-            return new TimeSignal(sourceId, time);
+            return new PhoneTimeSuggestion(phoneId, time);
         }
 
         static class Builder {
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 45fef76..37da018 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -28,7 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.timedetector.TimeSignal;
+import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.util.TimestampedValue;
@@ -67,10 +67,10 @@
     public void testStubbedCall_withoutPermission() {
         doThrow(new SecurityException("Mock"))
                 .when(mMockContext).enforceCallingPermission(anyString(), any());
-        TimeSignal timeSignal = createNitzTimeSignal();
+        PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion();
 
         try {
-            mTimeDetectorService.suggestTime(timeSignal);
+            mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion);
         } finally {
             verify(mMockContext).enforceCallingPermission(
                     eq(android.Manifest.permission.SET_TIME), anyString());
@@ -78,15 +78,15 @@
     }
 
     @Test
-    public void testSuggestTime() {
+    public void testSuggestPhoneTime() {
         doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
 
-        TimeSignal timeSignal = createNitzTimeSignal();
-        mTimeDetectorService.suggestTime(timeSignal);
+        PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion();
+        mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion);
 
         verify(mMockContext)
                 .enforceCallingPermission(eq(android.Manifest.permission.SET_TIME), anyString());
-        mStubbedTimeDetectorStrategy.verifySuggestTimeCalled(timeSignal);
+        mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion);
     }
 
     @Test
@@ -115,15 +115,16 @@
         mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(false);
     }
 
-    private static TimeSignal createNitzTimeSignal() {
+    private static PhoneTimeSuggestion createPhoneTimeSuggestion() {
+        int phoneId = 1234;
         TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
-        return new TimeSignal(TimeSignal.SOURCE_ID_NITZ, timeValue);
+        return new PhoneTimeSuggestion(phoneId, timeValue);
     }
 
     private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
 
         // Call tracking.
-        private TimeSignal mLastSuggestedTime;
+        private PhoneTimeSuggestion mLastPhoneSuggestion;
         private Boolean mLastAutoTimeDetectionToggle;
         private boolean mDumpCalled;
 
@@ -132,9 +133,9 @@
         }
 
         @Override
-        public void suggestTime(TimeSignal timeSignal) {
+        public void suggestPhoneTime(PhoneTimeSuggestion timeSuggestion) {
             resetCallTracking();
-            mLastSuggestedTime = timeSignal;
+            mLastPhoneSuggestion = timeSuggestion;
         }
 
         @Override
@@ -150,13 +151,13 @@
         }
 
         void resetCallTracking() {
-            mLastSuggestedTime = null;
+            mLastPhoneSuggestion = null;
             mLastAutoTimeDetectionToggle = null;
             mDumpCalled = false;
         }
 
-        void verifySuggestTimeCalled(TimeSignal expectedSignal) {
-            assertEquals(expectedSignal, mLastSuggestedTime);
+        void verifySuggestPhoneTimeCalled(PhoneTimeSuggestion expectedSignal) {
+            assertEquals(expectedSignal, mLastPhoneSuggestion);
         }
 
         void verifyHandleAutoTimeDetectionToggleCalled(boolean expectedEnable) {
diff --git a/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
new file mode 100644
index 0000000..f1b2ef8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/usage/IntervalStatsTests.java
@@ -0,0 +1,191 @@
+/*
+ * 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.usage;
+
+import static android.app.usage.UsageEvents.Event.MAX_EVENT_TYPE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import android.app.usage.UsageEvents;
+import android.content.res.Configuration;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IntervalStatsTests {
+    private static final int NUMBER_OF_PACKAGES = 7;
+    private static final int NUMBER_OF_EVENTS_PER_PACKAGE = 200;
+    private static final int NUMBER_OF_EVENTS = NUMBER_OF_PACKAGES * NUMBER_OF_EVENTS_PER_PACKAGE;
+
+    private long mEndTime = 0;
+
+    private void populateIntervalStats(IntervalStats intervalStats) {
+        final int timeProgression = 23;
+        long time = System.currentTimeMillis() - (NUMBER_OF_EVENTS * timeProgression);
+
+        intervalStats.majorVersion = 7;
+        intervalStats.minorVersion = 8;
+        intervalStats.beginTime = time;
+        intervalStats.interactiveTracker.count = 2;
+        intervalStats.interactiveTracker.duration = 111111;
+        intervalStats.nonInteractiveTracker.count = 3;
+        intervalStats.nonInteractiveTracker.duration = 222222;
+        intervalStats.keyguardShownTracker.count = 4;
+        intervalStats.keyguardShownTracker.duration = 333333;
+        intervalStats.keyguardHiddenTracker.count = 5;
+        intervalStats.keyguardHiddenTracker.duration = 4444444;
+
+        for (int i = 0; i < NUMBER_OF_EVENTS; i++) {
+            UsageEvents.Event event = new UsageEvents.Event();
+            final int packageInt = ((i / 3) % NUMBER_OF_PACKAGES); // clusters of 3 events
+            event.mPackage = "fake.package.name" + packageInt;
+            if (packageInt == 3) {
+                // Third app is an instant app
+                event.mFlags |= UsageEvents.Event.FLAG_IS_PACKAGE_INSTANT_APP;
+            }
+
+            final int instanceId = i % 11;
+            event.mClass = ".fake.class.name" + instanceId;
+            event.mTimeStamp = time;
+            event.mEventType = i % (MAX_EVENT_TYPE + 1); //"random" event type
+            event.mInstanceId = instanceId;
+
+
+            final int rootPackageInt = (i % 5); // 5 "apps" start each task
+            event.mTaskRootPackage = "fake.package.name" + rootPackageInt;
+
+            final int rootClassInt = i % 6;
+            event.mTaskRootClass = ".fake.class.name" + rootClassInt;
+
+            switch (event.mEventType) {
+                case UsageEvents.Event.CONFIGURATION_CHANGE:
+                    event.mConfiguration = new Configuration(); //empty config
+                    break;
+                case UsageEvents.Event.SHORTCUT_INVOCATION:
+                    event.mShortcutId = "shortcut" + (i % 8); //"random" shortcut
+                    break;
+                case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
+                    //"random" bucket and reason
+                    event.mBucketAndReason = (((i % 5 + 1) * 10) << 16) & (i % 5 + 1) << 8;
+                    break;
+                case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                    event.mNotificationChannelId = "channel" + (i % 5); //"random" channel
+                    break;
+            }
+
+            intervalStats.addEvent(event);
+            intervalStats.update(event.mPackage, event.mClass, event.mTimeStamp, event.mEventType,
+                    event.mInstanceId);
+
+            time += timeProgression; // Arbitrary progression of time
+        }
+        mEndTime = time;
+
+        final Configuration config1 = new Configuration();
+        config1.fontScale = 3.3f;
+        config1.mcc = 4;
+        intervalStats.getOrCreateConfigurationStats(config1);
+
+        final Configuration config2 = new Configuration();
+        config2.mnc = 5;
+        config2.setLocale(new Locale("en", "US"));
+        intervalStats.getOrCreateConfigurationStats(config2);
+
+        intervalStats.activeConfiguration = config2;
+    }
+
+    @Test
+    public void testObfuscation() {
+        final IntervalStats intervalStats = new IntervalStats();
+        populateIntervalStats(intervalStats);
+
+        final PackagesTokenData packagesTokenData = new PackagesTokenData();
+        intervalStats.obfuscateData(packagesTokenData);
+
+        // data is populated with 7 different "apps"
+        assertEquals(packagesTokenData.tokensToPackagesMap.size(), NUMBER_OF_PACKAGES);
+        assertEquals(packagesTokenData.packagesToTokensMap.size(), NUMBER_OF_PACKAGES);
+        assertEquals(packagesTokenData.counter, NUMBER_OF_PACKAGES + 1);
+
+        assertEquals(intervalStats.events.size(), NUMBER_OF_EVENTS);
+        assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
+    }
+
+    @Test
+    public void testDeobfuscation() {
+        final IntervalStats intervalStats = new IntervalStats();
+        populateIntervalStats(intervalStats);
+
+        final PackagesTokenData packagesTokenData = new PackagesTokenData();
+        intervalStats.obfuscateData(packagesTokenData);
+        intervalStats.deobfuscateData(packagesTokenData);
+
+        // ensure deobfuscation doesn't update any of the mappings data
+        assertEquals(packagesTokenData.tokensToPackagesMap.size(), NUMBER_OF_PACKAGES);
+        assertEquals(packagesTokenData.packagesToTokensMap.size(), NUMBER_OF_PACKAGES);
+        assertEquals(packagesTokenData.counter, NUMBER_OF_PACKAGES + 1);
+
+        // ensure deobfuscation didn't remove any events or usage stats
+        assertEquals(intervalStats.events.size(), NUMBER_OF_EVENTS);
+        assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
+    }
+
+    @Test
+    public void testBadDataOnDeobfuscation() {
+        final IntervalStats intervalStats = new IntervalStats();
+        populateIntervalStats(intervalStats);
+
+        final PackagesTokenData packagesTokenData = new PackagesTokenData();
+        intervalStats.obfuscateData(packagesTokenData);
+        intervalStats.packageStats.clear();
+
+        // remove the mapping for token 2
+        packagesTokenData.tokensToPackagesMap.remove(2);
+
+        intervalStats.deobfuscateData(packagesTokenData);
+        // deobfuscation should have removed all events mapped to package token 2
+        assertEquals(intervalStats.events.size(),
+                NUMBER_OF_EVENTS - NUMBER_OF_EVENTS_PER_PACKAGE - 1);
+        assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES - 1);
+    }
+
+    @Test
+    public void testBadPackageDataOnDeobfuscation() {
+        final IntervalStats intervalStats = new IntervalStats();
+        populateIntervalStats(intervalStats);
+
+        final PackagesTokenData packagesTokenData = new PackagesTokenData();
+        intervalStats.obfuscateData(packagesTokenData);
+        intervalStats.packageStats.clear();
+
+        // remove mapping number 2 within package 3 (random)
+        packagesTokenData.tokensToPackagesMap.valueAt(3).remove(2);
+
+        intervalStats.deobfuscateData(packagesTokenData);
+        // deobfuscation should not have removed all events for a package - however, it's possible
+        // that some events were removed because of how shortcut and notification events are handled
+        assertTrue(intervalStats.events.size() > NUMBER_OF_EVENTS - NUMBER_OF_EVENTS_PER_PACKAGE);
+        assertEquals(intervalStats.packageStats.size(), NUMBER_OF_PACKAGES);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index c55f459..df0c37a 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -48,7 +48,7 @@
 @SmallTest
 public class UsageStatsDatabaseTest {
 
-    private static final int MAX_TESTED_VERSION = 4;
+    private static final int MAX_TESTED_VERSION = 5;
     protected Context mContext;
     private UsageStatsDatabase mUsageStatsDatabase;
     private File mTestDir;
@@ -74,6 +74,7 @@
         mContext = InstrumentationRegistry.getTargetContext();
         mTestDir = new File(mContext.getFilesDir(), "UsageStatsDatabaseTest");
         mUsageStatsDatabase = new UsageStatsDatabase(mTestDir);
+        mUsageStatsDatabase.readMappingsLocked();
         mUsageStatsDatabase.init(1);
         populateIntervalStats();
         clearUsageStatsFiles();
@@ -259,6 +260,24 @@
 
     void compareUsageEvent(Event e1, Event e2, int debugId, int minVersion) {
         switch (minVersion) {
+            case 5: // test fields added in version 5
+                assertEquals(e1.mPackageToken, e2.mPackageToken, "Usage event " + debugId);
+                assertEquals(e1.mClassToken, e2.mClassToken, "Usage event " + debugId);
+                assertEquals(e1.mTaskRootPackageToken, e2.mTaskRootPackageToken,
+                        "Usage event " + debugId);
+                assertEquals(e1.mTaskRootClassToken, e2.mTaskRootClassToken,
+                        "Usage event " + debugId);
+                switch (e1.mEventType) {
+                    case Event.SHORTCUT_INVOCATION:
+                        assertEquals(e1.mShortcutIdToken, e2.mShortcutIdToken,
+                                "Usage event " + debugId);
+                        break;
+                    case Event.NOTIFICATION_INTERRUPTION:
+                        assertEquals(e1.mNotificationChannelIdToken, e2.mNotificationChannelIdToken,
+                                "Usage event " + debugId);
+                        break;
+                }
+                // fallthrough
             case 4: // test fields added in version 4
                 assertEquals(e1.mInstanceId, e2.mInstanceId, "Usage event " + debugId);
                 assertEquals(e1.mTaskRootPackage, e2.mTaskRootPackage, "Usage event " + debugId);
@@ -370,11 +389,16 @@
     void runVersionChangeTest(int oldVersion, int newVersion, int interval) throws IOException {
         // Write IntervalStats to disk in old version format
         UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir, oldVersion);
+        prevDB.readMappingsLocked();
         prevDB.init(1);
         prevDB.putUsageStats(interval, mIntervalStats);
+        if (oldVersion >= 5) {
+            prevDB.writeMappingsLocked();
+        }
 
         // Simulate an upgrade to a new version and read from the disk
         UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir, newVersion);
+        newDB.readMappingsLocked();
         newDB.init(mEndTime);
         List<IntervalStats> stats = newDB.queryUsageStats(interval, 0, mEndTime,
                 mIntervalStatsVerifier);
@@ -394,6 +418,7 @@
      */
     void runBackupRestoreTest(int version) throws IOException {
         UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir);
+        prevDB.readMappingsLocked();
         prevDB.init(1);
         prevDB.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats);
         // Create a backup with a specific version
@@ -402,6 +427,7 @@
         clearUsageStatsFiles();
 
         UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir);
+        newDB.readMappingsLocked();
         newDB.init(1);
         // Attempt to restore the usage stats from the backup
         newDB.applyRestoredPayload(KEY_USAGE_STATS, blob);
@@ -438,6 +464,28 @@
         runVersionChangeTest(3, 4, UsageStatsManager.INTERVAL_YEARLY);
     }
 
+    /**
+     * Test the version upgrade from 4 to 5
+     */
+    @Test
+    public void testVersionUpgradeFrom4to5() throws IOException {
+        runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_DAILY);
+        runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_WEEKLY);
+        runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_MONTHLY);
+        runVersionChangeTest(4, 5, UsageStatsManager.INTERVAL_YEARLY);
+    }
+
+    /**
+     * Test the version upgrade from 3 to 5
+     */
+    @Test
+    public void testVersionUpgradeFrom3to5() throws IOException {
+        runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_DAILY);
+        runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_WEEKLY);
+        runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_MONTHLY);
+        runVersionChangeTest(3, 5, UsageStatsManager.INTERVAL_YEARLY);
+    }
+
 
     /**
      * Test the version upgrade from 3 to 4
@@ -492,4 +540,68 @@
             assertEquals(extra, files.keyAt(0));
         }
     }
+
+    private void compareObfuscatedData(int interval) throws IOException {
+        // Write IntervalStats to disk
+        UsageStatsDatabase prevDB = new UsageStatsDatabase(mTestDir, 5);
+        prevDB.readMappingsLocked();
+        prevDB.init(1);
+        prevDB.putUsageStats(interval, mIntervalStats);
+        prevDB.writeMappingsLocked();
+
+        // Read IntervalStats from disk into a new db
+        UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir, 5);
+        newDB.readMappingsLocked();
+        newDB.init(mEndTime);
+        List<IntervalStats> stats = newDB.queryUsageStats(interval, 0, mEndTime,
+                mIntervalStatsVerifier);
+
+        assertEquals(1, stats.size());
+        // The written and read IntervalStats should match
+        compareIntervalStats(mIntervalStats, stats.get(0), 5);
+    }
+
+    @Test
+    public void testObfuscation() throws IOException {
+        compareObfuscatedData(UsageStatsManager.INTERVAL_DAILY);
+        compareObfuscatedData(UsageStatsManager.INTERVAL_WEEKLY);
+        compareObfuscatedData(UsageStatsManager.INTERVAL_MONTHLY);
+        compareObfuscatedData(UsageStatsManager.INTERVAL_YEARLY);
+    }
+
+    private void verifyPackageNotRetained(int interval) throws IOException {
+        UsageStatsDatabase db = new UsageStatsDatabase(mTestDir, 5);
+        db.readMappingsLocked();
+        db.init(1);
+        db.putUsageStats(interval, mIntervalStats);
+
+        final String removedPackage = "fake.package.name0";
+        // invoke handler call directly from test to remove package
+        db.onPackageRemoved(removedPackage, System.currentTimeMillis());
+
+        List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime,
+                mIntervalStatsVerifier);
+        for (int i = 0; i < stats.size(); i++) {
+            final IntervalStats stat = stats.get(i);
+            if (stat.packageStats.containsKey(removedPackage)) {
+                fail("Found removed package " + removedPackage + " in package stats.");
+                return;
+            }
+            for (int j = 0; j < stat.events.size(); j++) {
+                final Event event = stat.events.get(j);
+                if (removedPackage.equals(event.mPackage)) {
+                    fail("Found an event from removed package " + removedPackage);
+                    return;
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testPackageRetention() throws IOException {
+        verifyPackageNotRetained(UsageStatsManager.INTERVAL_DAILY);
+        verifyPackageNotRetained(UsageStatsManager.INTERVAL_WEEKLY);
+        verifyPackageNotRetained(UsageStatsManager.INTERVAL_MONTHLY);
+        verifyPackageNotRetained(UsageStatsManager.INTERVAL_YEARLY);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 2836e69..03367db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -18,8 +18,6 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -63,10 +61,8 @@
     private ActivityMetricsLaunchObserver mLaunchObserver;
     private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry;
 
-    private ActivityStack mStack;
-    private TaskRecord mTask;
-    private ActivityRecord mActivityRecord;
-    private ActivityRecord mActivityRecordTrampoline;
+    private ActivityRecord mTrampolineActivity;
+    private ActivityRecord mTopActivity;
 
     @Before
     public void setUpAMLO() {
@@ -80,15 +76,10 @@
 
         // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful.
         // This seems to be the easiest way to create an ActivityRecord.
-        mStack = new StackBuilder(mRootActivityContainer)
-                .setActivityType(ACTIVITY_TYPE_STANDARD)
-                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
-                .setOnTop(true)
-                .setCreateActivity(true)
+        mTrampolineActivity = new ActivityBuilder(mService).setCreateTask(true).build();
+        mTopActivity = new ActivityBuilder(mService)
+                .setTask(mTrampolineActivity.getTaskRecord())
                 .build();
-        mTask = mStack.topTask();
-        mActivityRecord = mTask.getTopActivity();
-        mActivityRecordTrampoline = new ActivityBuilder(mService).setTask(mTask).build();
     }
 
     @After
@@ -123,8 +114,7 @@
         return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
     }
 
-    @Test
-    public void testOnIntentStarted() throws Exception {
+    private void onIntentStarted() {
         Intent intent = new Intent("action 1");
 
         mActivityMetricsLogger.notifyActivityLaunching(intent);
@@ -134,123 +124,120 @@
     }
 
     @Test
-    public void testOnIntentFailed() throws Exception {
-        testOnIntentStarted();
-
-        ActivityRecord activityRecord = null;
+    public void testOnIntentFailed() {
+        onIntentStarted();
 
         // Bringing an intent that's already running 'to front' is not considered
         // as an ACTIVITY_LAUNCHED state transition.
         mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
-                activityRecord);
+                null /* launchedActivity */);
 
         verifyAsync(mLaunchObserver).onIntentFailed();
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
-    @Test
-    public void testOnActivityLaunched() throws Exception {
-        testOnIntentStarted();
+    private void onActivityLaunched() {
+        onIntentStarted();
 
-        mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS,
-                mActivityRecord);
+        mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTopActivity);
 
-        verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mActivityRecord), anyInt());
+        verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTopActivity), anyInt());
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
     @Test
-    public void testOnActivityLaunchFinished() throws Exception {
-       testOnActivityLaunched();
+    public void testOnActivityLaunchFinished() {
+        onActivityLaunched();
 
-       mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
-               SystemClock.elapsedRealtimeNanos());
+        mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
+                SystemClock.elapsedRealtimeNanos());
 
-       mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecord.getWindowingMode(),
-               SystemClock.elapsedRealtimeNanos());
+        notifyWindowsDrawn(mTopActivity);
 
-       verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord), anyLong());
-       verifyNoMoreInteractions(mLaunchObserver);
-    }
-
-    @Test
-    public void testOnActivityLaunchCancelled() throws Exception {
-       testOnActivityLaunched();
-
-       mActivityRecord.mDrawn = true;
-
-       // Cannot time already-visible activities.
-       mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mActivityRecord);
-
-       verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mActivityRecord));
-       verifyNoMoreInteractions(mLaunchObserver);
-    }
-
-    @Test
-    public void testOnReportFullyDrawn() throws Exception {
-        testOnActivityLaunched();
-
-        mActivityMetricsLogger.logAppTransitionReportedDrawn(mActivityRecord, false);
-
-        verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mActivityRecord), anyLong());
+        verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
     @Test
-    public void testOnActivityLaunchedTrampoline() throws Exception {
-        testOnIntentStarted();
+    public void testOnActivityLaunchCancelled() {
+        onActivityLaunched();
 
-        mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS,
-                mActivityRecord);
+        mTopActivity.mDrawn = true;
 
-        verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mActivityRecord), anyInt());
+        // Cannot time already-visible activities.
+        mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
+
+        verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity));
+        verifyNoMoreInteractions(mLaunchObserver);
+    }
+
+    @Test
+    public void testOnReportFullyDrawn() {
+        onActivityLaunched();
+
+        mActivityMetricsLogger.logAppTransitionReportedDrawn(mTopActivity, false);
+
+        verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong());
+        verifyNoMoreInteractions(mLaunchObserver);
+    }
+
+    private void onActivityLaunchedTrampoline() {
+        onIntentStarted();
+
+        mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTopActivity);
+
+        verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTopActivity), anyInt());
 
         // A second, distinct, activity launch is coalesced into the the current app launch sequence
-        mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS,
-                mActivityRecordTrampoline);
+        mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTrampolineActivity);
 
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
-    @Test
-    public void testOnActivityLaunchFinishedTrampoline() throws Exception {
-       testOnActivityLaunchedTrampoline();
-
-       mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
-               SystemClock.elapsedRealtimeNanos());
-
-       mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecordTrampoline.getWindowingMode(),
-               SystemClock.elapsedRealtimeNanos());
-
-       verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline),
-                                                             anyLong());
-       verifyNoMoreInteractions(mLaunchObserver);
+    private void notifyWindowsDrawn(ActivityRecord r) {
+        mActivityMetricsLogger.notifyWindowsDrawn(r.getWindowingMode(),
+                SystemClock.elapsedRealtimeNanos());
     }
 
     @Test
-    public void testOnActivityLaunchCancelledTrampoline() throws Exception {
-       testOnActivityLaunchedTrampoline();
+    public void testOnActivityLaunchFinishedTrampoline() {
+        onActivityLaunchedTrampoline();
 
-       mActivityRecordTrampoline.mDrawn = true;
+        mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
+                SystemClock.elapsedRealtimeNanos());
 
-       // Cannot time already-visible activities.
-       mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
-               mActivityRecordTrampoline);
+        notifyWindowsDrawn(mTrampolineActivity);
 
-       verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mActivityRecordTrampoline));
-       verifyNoMoreInteractions(mLaunchObserver);
+        notifyWindowsDrawn(mTopActivity);
+
+        verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTrampolineActivity),
+                anyLong());
+        verifyNoMoreInteractions(mLaunchObserver);
     }
 
     @Test
-    public void testActivityRecordProtoIsNotTooBig() throws Exception {
+    public void testOnActivityLaunchCancelledTrampoline() {
+        onActivityLaunchedTrampoline();
+
+        mTrampolineActivity.mDrawn = true;
+
+        // Cannot time already-visible activities.
+        mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTrampolineActivity);
+
+        verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTrampolineActivity));
+        verifyNoMoreInteractions(mLaunchObserver);
+    }
+
+    @Test
+    public void testActivityRecordProtoIsNotTooBig() {
         // The ActivityRecordProto must not be too big, otherwise converting it at runtime
         // will become prohibitively expensive.
-        assertWithMessage("mActivityRecord: %s", mActivityRecord).
-                that(activityRecordToProto(mActivityRecord).length).
-                isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
+        assertWithMessage("mTopActivity: %s", mTopActivity)
+                .that(activityRecordToProto(mTopActivity).length)
+                .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
 
-        assertWithMessage("mActivityRecordTrampoline: %s", mActivityRecordTrampoline).
-                that(activityRecordToProto(mActivityRecordTrampoline).length).
-                isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
+        assertWithMessage("mTrampolineActivity: %s", mTrampolineActivity)
+                .that(activityRecordToProto(mTrampolineActivity).length)
+                .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 7449efd..1fb6a56 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -60,11 +60,9 @@
     private ActivityRecord mActivity;
 
     public void setUpOnDisplay(DisplayContent dc) {
-        mStack = createTaskStackOnDisplay(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, dc);
-        mTask = createTaskInStack(mStack, 0 /* userId */);
-        mActivity = WindowTestUtils.createTestActivityRecord(dc);
-
-        mTask.addChild(mActivity, 0);
+        mActivity = createTestActivityRecord(dc, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
+        mTask = mActivity.getTask();
+        mStack = mTask.mStack;
 
         // Set a remote animator with snapshot disabled. Snapshots don't work in wmtests.
         RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
@@ -167,7 +165,7 @@
         // setup currently defaults to no snapshot.
         setUpOnDisplay(mDisplayContent);
 
-        mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        mTask.mTaskRecord.setWindowingMode(WINDOWING_MODE_FREEFORM);
         assertEquals(1, mDisplayContent.mChangingApps.size());
         assertTrue(mActivity.isInChangeTransition());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
index acbbc46..d1510cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -44,14 +44,32 @@
     @Test
     public void testProtoLogToolIntegration() {
         ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
-        ProtoLogImpl.setSingleInstance(mockedProtoLog);
-        ProtoLogGroup.testProtoLog();
-        verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(
-                ProtoLogGroup.TEST_GROUP),
+        runWith(mockedProtoLog, () -> {
+            ProtoLogGroup.testProtoLog();
+        });
+        verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(ProtoLogGroup.TEST_GROUP),
                 anyInt(), eq(0b0010101001010111),
                 eq(ProtoLogGroup.TEST_GROUP.isLogToLogcat()
                         ? "Test completed successfully: %b %d %o %x %e %g %f %% %s"
                         : null),
                 eq(new Object[]{true, 1L, 2L, 3L, 0.4, 0.5, 0.6, "ok"}));
     }
+
+    /**
+     * Starts protolog for the duration of {@code runnable}, with a ProtoLogImpl instance installed.
+     */
+    private void runWith(ProtoLogImpl mockInstance, Runnable runnable) {
+        ProtoLogImpl original = ProtoLogImpl.getSingleInstance();
+        original.startProtoLog(null);
+        try {
+            ProtoLogImpl.setSingleInstance(mockInstance);
+            try {
+                runnable.run();
+            } finally {
+                ProtoLogImpl.setSingleInstance(original);
+            }
+        } finally {
+            original.stopProtoLog(null, false);
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index fff77f5..7026004 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -152,7 +152,7 @@
         hiddenActivity.setHidden(true);
         mDisplayContent.getConfiguration().windowConfiguration.setRotation(
                 mDisplayContent.getRotation());
-        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeActivity);
 
         // Ensure that we are animating the target activity as well
         assertTrue(mController.isAnimatingTask(homeActivity.getTask()));
@@ -181,7 +181,7 @@
 
         mDisplayContent.getConfiguration().windowConfiguration.setRotation(
                 mDisplayContent.getRotation());
-        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeAppWindow);
         mController.startAnimation();
 
         // Ensure that we are animating the app and wallpaper target
@@ -210,7 +210,7 @@
 
         mDisplayContent.getConfiguration().windowConfiguration.setRotation(
                 mDisplayContent.getRotation());
-        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeActivity);
         mController.startAnimation();
 
         // Cancel the animation and ensure the controller is still running
@@ -242,7 +242,7 @@
         doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
 
         // Start and finish the animation
-        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeActivity);
         mController.startAnimation();
         // Reset at this point since we may remove adapters that couldn't be created
         reset(mController);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index ebedde7..839ddb2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -78,7 +78,7 @@
         mRecentsAnimationController = mock(RecentsAnimationController.class);
         mService.mWindowManager.setRecentsAnimationController(mRecentsAnimationController);
         doNothing().when(mService.mWindowManager).initializeRecentsAnimation(
-                anyInt(), any(), any(), anyInt(), any());
+                anyInt(), any(), any(), anyInt(), any(), any());
         doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation();
 
         final RecentTasks recentTasks = mService.getRecentTasks();
@@ -385,7 +385,8 @@
                 return null;
             }).when(mService.mWindowManager).initializeRecentsAnimation(
                     anyInt() /* targetActivityType */, any() /* recentsAnimationRunner */,
-                    any() /* callbacks */, anyInt() /* displayId */, any() /* recentTaskIds */);
+                    any() /* callbacks */, anyInt() /* displayId */, any() /* recentTaskIds */,
+                    any() /* targetActivity */);
         }
 
         Intent recentsIntent = new Intent();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 5b9a785..2fc03c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -21,12 +21,15 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
@@ -119,7 +122,7 @@
     @Test
     public void testRemoveContainer() {
         final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
 
         assertNotNull(stack);
         assertNotNull(task);
@@ -135,10 +138,10 @@
     @Test
     public void testRemoveContainer_deferRemoval() {
         final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
 
         // Stack removal is deferred if one of its child is animating.
-        task.setLocalIsAnimating(true);
+        doReturn(true).when(task).isSelfAnimating();
 
         stack.removeIfPossible();
         // For the case of deferred removal the task controller will still be connected to the its
@@ -157,8 +160,7 @@
     public void testReparent() {
         // Create first stack on primary display.
         final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTask task1 = WindowTestUtils.createTestTask(stack1);
-        task1.mOnDisplayChangedCalled = false;
+        final Task task1 = createTaskInStack(stack1, 0 /* userId */);
 
         // Create second display and put second stack on it.
         final DisplayContent dc = createNewDisplay();
@@ -170,7 +172,7 @@
         final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1);
         final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2);
         assertEquals(stack1PositionInParent, stack2PositionInParent + 1);
-        assertTrue(task1.mOnDisplayChangedCalled);
+        verify(task1, times(1)).onDisplayChanged(any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 2ba1834..4dfa266 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -16,11 +16,16 @@
 
 package com.android.server.wm;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -45,7 +50,7 @@
     @Test
     public void testRemoveContainer() {
         final TaskStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+        final Task task = createTaskInStack(stackController1, 0 /* userId */);
         final ActivityRecord activity =
                 WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
 
@@ -59,11 +64,11 @@
     @Test
     public void testRemoveContainer_deferRemoval() {
         final TaskStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+        final Task task = createTaskInStack(stackController1, 0 /* userId */);
         final ActivityRecord activity =
                 WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
 
-        task.mShouldDeferRemoval = true;
+        doReturn(true).when(task).shouldDeferRemoval();
 
         task.removeIfPossible();
         // For the case of deferred removal the task will still be connected to the its app token
@@ -81,9 +86,9 @@
     @Test
     public void testReparent() {
         final TaskStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stackController1);
+        final Task task = createTaskInStack(stackController1, 0 /* userId */);
         final TaskStack stackController2 = createTaskStackOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stackController2);
+        final Task task2 = createTaskInStack(stackController2, 0 /* userId */);
 
         boolean gotException = false;
         try {
@@ -104,34 +109,33 @@
 
         task.reparent(stackController2, 0, false/* moveParents */);
         assertEquals(stackController2, task.getParent());
-        assertEquals(0, task.positionInParent());
-        assertEquals(1, task2.positionInParent());
+        assertEquals(0, task.getParent().mChildren.indexOf(task));
+        assertEquals(1, task2.getParent().mChildren.indexOf(task2));
     }
 
     @Test
     public void testReparent_BetweenDisplays() {
         // Create first stack on primary display.
         final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack1);
-        task.mOnDisplayChangedCalled = false;
+        final Task task = createTaskInStack(stack1, 0 /* userId */);
         assertEquals(mDisplayContent, stack1.getDisplayContent());
 
         // Create second display and put second stack on it.
         final DisplayContent dc = createNewDisplay();
         final TaskStack stack2 = createTaskStackOnDisplay(dc);
-        final WindowTestUtils.TestTask task2 = WindowTestUtils.createTestTask(stack2);
+        final Task task2 = createTaskInStack(stack2, 0 /* userId */);
         // Reparent and check state
         task.reparent(stack2, 0, false /* moveParents */);
         assertEquals(stack2, task.getParent());
-        assertEquals(0, task.positionInParent());
-        assertEquals(1, task2.positionInParent());
-        assertTrue(task.mOnDisplayChangedCalled);
+        assertEquals(0, task.getParent().mChildren.indexOf(task));
+        assertEquals(1, task2.getParent().mChildren.indexOf(task2));
+        verify(task, times(1)).onDisplayChanged(any());
     }
 
     @Test
     public void testBounds() {
         final TaskStack stack1 = createTaskStackOnDisplay(mDisplayContent);
-        final WindowTestUtils.TestTask task = WindowTestUtils.createTestTask(stack1);
+        final Task task = createTaskInStack(stack1, 0 /* userId */);
 
         // Check that setting bounds also updates surface position
         Rect bounds = new Rect(10, 10, 100, 200);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index 5c547c2..8cd97cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -16,24 +16,24 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
 import static android.view.DisplayCutout.fromBoundingRect;
-import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
 
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
 import android.view.Gravity;
-import android.view.IWindow;
 import android.view.WindowManager;
 
 import androidx.test.filters.FlakyTest;
@@ -57,29 +57,11 @@
 @RunWith(WindowTestRunner.class)
 public class WindowFrameTests extends WindowTestsBase {
 
-    private final IWindow mIWindow = new TestIWindow();
     private final Rect mEmptyRect = new Rect();
     private DisplayContent mTestDisplayContent;
 
-    static class FrameTestWindowState extends WindowState {
-        boolean mDockedResizingForTest = false;
-        FrameTestWindowState(WindowManagerService wm, IWindow iWindow, WindowToken windowToken,
-                WindowManager.LayoutParams attrs) {
-            super(wm, mock(Session.class), iWindow, windowToken, null, 0, 0, attrs, 0, 0,
-                    false /* ownerCanAddInternalSystemWindow */);
-        }
-
-        @Override
-        boolean isDockedResizing() {
-            return mDockedResizingForTest;
-        }
-    }
-
-    TaskStack mStubStack;
-
     @Before
     public void setUp() throws Exception {
-        mStubStack = mock(TaskStack.class);
         DisplayInfo testDisplayInfo = new DisplayInfo(mDisplayInfo);
         testDisplayInfo.displayCutout = null;
         mTestDisplayContent = createNewDisplay(testDisplayInfo);
@@ -129,8 +111,7 @@
                 expectedRect.bottom);
     }
 
-    private void assertPolicyCrop(
-            FrameTestWindowState w, int left, int top, int right, int bottom) {
+    private void assertPolicyCrop(WindowState w, int left, int top, int right, int bottom) {
         Rect policyCrop = new Rect();
         w.calculatePolicyCrop(policyCrop);
         assertRect(policyCrop, left, top, right, bottom);
@@ -139,7 +120,7 @@
     @Test
     public void testLayoutInFullscreenTaskInsets() {
         // fullscreen task doesn't use bounds for computeFrame
-        WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+        WindowState w = createWindow();
         w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
 
         final int bottomContentInset = 100;
@@ -196,7 +177,7 @@
     @Test
     public void testLayoutInFullscreenTaskNoInsets() {
         // fullscreen task doesn't use bounds for computeFrame
-        WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+        WindowState w = createWindow();
         w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
 
         // With no insets or system decor all the frames incoming from PhoneWindowManager
@@ -278,16 +259,16 @@
         final int logicalWidth = displayInfo.logicalWidth;
         final int logicalHeight = displayInfo.logicalHeight;
 
-        final int taskLeft = logicalWidth / 4;
-        final int taskTop = logicalHeight / 4;
-        final int taskRight = logicalWidth / 4 * 3;
-        final int taskBottom = logicalHeight / 4 * 3;
-        final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom);
-        WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+        final Rect taskBounds = new Rect(
+                logicalWidth / 4, logicalHeight / 4, logicalWidth / 4 * 3, logicalHeight / 4 * 3);
+        WindowState w = createWindow();
         final Task task = w.getTask();
         // Use split-screen because it is non-fullscreen, but also not floating
-        task.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        task.setBounds(taskBounds);
+        task.mTaskRecord.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        task.mTaskRecord.setBounds(taskBounds);
+        // The bounds we are requesting might be different from what the system resolved based on
+        // other factors.
+        final Rect resolvedTaskBounds = task.getBounds();
         w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
 
         final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
@@ -296,8 +277,8 @@
         w.computeFrameLw();
         // For non fullscreen tasks the containing frame is based off the
         // task bounds not the parent frame.
-        assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
-        assertContentFrame(w, taskBounds);
+        assertEquals(resolvedTaskBounds, w.getFrameLw());
+        assertContentFrame(w, resolvedTaskBounds);
         assertContentInset(w, 0, 0, 0, 0);
 
         pf.set(0, 0, logicalWidth, logicalHeight);
@@ -307,36 +288,38 @@
         final Rect cf = new Rect(0, 0, cfRight, cfBottom);
         windowFrames.setFrames(pf, pf, pf, cf, cf, pf, cf, mEmptyRect);
         w.computeFrameLw();
-        assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
-        int contentInsetRight = taskRight - cfRight;
-        int contentInsetBottom = taskBottom - cfBottom;
+        assertEquals(resolvedTaskBounds, w.getFrameLw());
+        int contentInsetRight = resolvedTaskBounds.right - cfRight;
+        int contentInsetBottom = resolvedTaskBounds.bottom - cfBottom;
         assertContentInset(w, 0, 0, contentInsetRight, contentInsetBottom);
-        assertContentFrame(w, new Rect(taskLeft, taskTop, taskRight - contentInsetRight,
-                taskBottom - contentInsetBottom));
+        assertContentFrame(w, new Rect(resolvedTaskBounds.left, resolvedTaskBounds.top,
+                resolvedTaskBounds.right - contentInsetRight,
+                resolvedTaskBounds.bottom - contentInsetBottom));
 
         pf.set(0, 0, logicalWidth, logicalHeight);
         // If we set displayed bounds, the insets will be computed with the main task bounds
         // but the frame will be positioned according to the displayed bounds.
         final int insetLeft = logicalWidth / 5;
         final int insetTop = logicalHeight / 5;
-        final int insetRight = insetLeft + (taskRight - taskLeft);
-        final int insetBottom = insetTop + (taskBottom - taskTop);
-        task.setOverrideDisplayedBounds(taskBounds);
-        task.setBounds(insetLeft, insetTop, insetRight, insetBottom);
+        final int insetRight = insetLeft + (resolvedTaskBounds.right - resolvedTaskBounds.left);
+        final int insetBottom = insetTop + (resolvedTaskBounds.bottom - resolvedTaskBounds.top);
+        task.mTaskRecord.setDisplayedBounds(resolvedTaskBounds);
+        task.mTaskRecord.setBounds(insetLeft, insetTop, insetRight, insetBottom);
         windowFrames.setFrames(pf, pf, pf, cf, cf, pf, cf, mEmptyRect);
         w.computeFrameLw();
-        assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
+        assertEquals(resolvedTaskBounds, w.getFrameLw());
         contentInsetRight = insetRight - cfRight;
         contentInsetBottom = insetBottom - cfBottom;
         assertContentInset(w, 0, 0, contentInsetRight, contentInsetBottom);
-        assertContentFrame(w, new Rect(taskLeft, taskTop, taskRight - contentInsetRight,
-                taskBottom - contentInsetBottom));
+        assertContentFrame(w, new Rect(resolvedTaskBounds.left, resolvedTaskBounds.top,
+                resolvedTaskBounds.right - contentInsetRight,
+                resolvedTaskBounds.bottom - contentInsetBottom));
     }
 
     @Test
     @FlakyTest(bugId = 130388666)
     public void testCalculatePolicyCrop() {
-        final FrameTestWindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+        final WindowState w = createWindow();
         w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
 
         final DisplayInfo displayInfo = w.getDisplayContent().getDisplayInfo();
@@ -381,7 +364,7 @@
         // to the computed window frame.
         assertPolicyCrop(w, 0, 0, logicalWidth / 2, logicalHeight / 2);
 
-        w.mDockedResizingForTest = true;
+        doReturn(true).when(w).isDockedResizing();
         // But if we are docked resizing it won't be, however we will still be
         // shrunk to the decor frame and the display.
         assertPolicyCrop(w, 0, 0,
@@ -402,7 +385,7 @@
         final int taskRight = logicalWidth / 4 * 3;
         final int taskBottom = logicalHeight / 4 * 3;
         final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom);
-        WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+        WindowState w = createWindow();
         final Task task = w.getTask();
         // Use split-screen because it is non-fullscreen, but also not floating
         task.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
@@ -440,7 +423,7 @@
     @FlakyTest(bugId = 130388666)
     public void testDisplayCutout() {
         // Regular fullscreen task and window
-        WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+        WindowState w = createWindow();
         w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
 
         final Rect pf = new Rect(0, 0, 1000, 2000);
@@ -464,7 +447,7 @@
     @FlakyTest(bugId = 130388666)
     public void testDisplayCutout_tempDisplayedBounds() {
         // Regular fullscreen task and window
-        WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+        WindowState w = createWindow();
         final Task task = w.getTask();
         task.setBounds(new Rect(0, 0, 1000, 2000));
         task.setOverrideDisplayedBounds(new Rect(0, -500, 1000, 1500));
@@ -489,11 +472,12 @@
 
     @Test
     public void testFreeformContentInsets() {
+        removeGlobalMinSizeRestriction();
         // fullscreen task doesn't use bounds for computeFrame
-        WindowState w = createWindow(MATCH_PARENT, MATCH_PARENT);
+        WindowState w = createWindow();
         final Task task = w.getTask();
         w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
-        task.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        task.mTaskRecord.setWindowingMode(WINDOWING_MODE_FREEFORM);
 
         DisplayContent dc = mTestDisplayContent;
         dc.mInputMethodTarget = w;
@@ -515,7 +499,7 @@
         // First check that it only gets moved up enough to show window.
         final Rect winRect = new Rect(200, 200, 300, 500);
 
-        task.setBounds(winRect);
+        task.mTaskRecord.setBounds(winRect);
         w.getWindowFrames().setFrames(pf, df, of, cf, vf, dcf, sf, mEmptyRect);
         w.computeFrameLw();
 
@@ -527,7 +511,7 @@
 
         // Now check that it won't get moved beyond the top and then has appropriate insets
         winRect.bottom = 600;
-        task.setBounds(winRect);
+        task.mTaskRecord.setBounds(winRect);
         w.setBounds(winRect);
         w.getWindowFrames().setFrames(pf, df, of, cf, vf, dcf, sf, mEmptyRect);
         w.computeFrameLw();
@@ -544,16 +528,9 @@
         assertEquals(winRect, w.getFrameLw());
     }
 
-    private FrameTestWindowState createWindow(int width, int height) {
-        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
-        attrs.width = width;
-        attrs.height = height;
-
-        ActivityRecord activity = createActivityRecord(mTestDisplayContent,
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-
-        FrameTestWindowState ws = new FrameTestWindowState(mWm, mIWindow, activity, attrs);
-        activity.addWindow(ws);
+    private WindowState createWindow() {
+        final WindowState ws = createWindow(null, TYPE_APPLICATION, mTestDisplayContent, "WindowFrameTests");
+        spyOn(ws);
         return ws;
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 4fbf820..51daf65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -33,16 +33,16 @@
  * A collection of static functions that provide access to WindowManager related test functionality.
  */
 class WindowTestUtils {
-    private static int sNextTaskId = 0;
 
     /** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */
-    static Task createTaskInStack(WindowManagerService service, TaskStack stack,
-            int userId) {
+    static Task createTaskInStack(WindowManagerService service, TaskStack stack, int userId) {
         synchronized (service.mGlobalLock) {
-            final Task newTask = new Task(sNextTaskId++, stack, userId, service, 0, false,
-                    new ActivityManager.TaskDescription(), null);
-            stack.addTask(newTask, POSITION_TOP);
-            return newTask;
+            final TaskRecord task = new ActivityTestsBase.TaskBuilder(
+                    stack.mActivityStack.mStackSupervisor)
+                    .setUserId(userId)
+                    .setStack(stack.mActivityStack)
+                    .build();
+            return task.mTask;
         }
     }
 
@@ -53,17 +53,33 @@
         return activity;
     }
 
+    static ActivityRecord createTestActivityRecord(ActivityStack stack) {
+        synchronized (stack.mService.mGlobalLock) {
+            final ActivityRecord activity = new ActivityTestsBase.ActivityBuilder(
+                    stack.mService)
+                    .setStack(stack)
+                    .setCreateTask(true)
+                    .build();
+            postCreateActivitySetup(activity, stack.mTaskStack.getDisplayContent());
+            return activity;
+        }
+    }
+
     static ActivityRecord createTestActivityRecord(DisplayContent dc) {
         synchronized (dc.mWmService.mGlobalLock) {
             final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build();
-            activity.onDisplayChanged(dc);
-            activity.setOccludesParent(true);
-            activity.setHidden(false);
-            activity.hiddenRequested = false;
+            postCreateActivitySetup(activity, dc);
             return activity;
         }
     }
 
+    private static void postCreateActivitySetup(ActivityRecord activity, DisplayContent dc) {
+        activity.onDisplayChanged(dc);
+        activity.setOccludesParent(true);
+        activity.setHidden(false);
+        activity.hiddenRequested = false;
+    }
+
     static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
         return createTestWindowToken(type, dc, false /* persistOnEmpty */);
     }
@@ -92,49 +108,6 @@
         }
     }
 
-    /* Used so we can gain access to some protected members of the {@link Task} class */
-    static class TestTask extends Task {
-        boolean mShouldDeferRemoval = false;
-        boolean mOnDisplayChangedCalled = false;
-        private boolean mIsAnimating = false;
-
-        TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service,
-                int resizeMode, boolean supportsPictureInPicture,
-                TaskRecord taskRecord) {
-            super(taskId, stack, userId, service, resizeMode, supportsPictureInPicture,
-                    new ActivityManager.TaskDescription(), taskRecord);
-            stack.addTask(this, POSITION_TOP);
-        }
-
-        boolean shouldDeferRemoval() {
-            return mShouldDeferRemoval;
-        }
-
-        int positionInParent() {
-            return getParent().mChildren.indexOf(this);
-        }
-
-        @Override
-        void onDisplayChanged(DisplayContent dc) {
-            super.onDisplayChanged(dc);
-            mOnDisplayChangedCalled = true;
-        }
-
-        @Override
-        boolean isSelfAnimating() {
-            return mIsAnimating;
-        }
-
-        void setLocalIsAnimating(boolean isAnimating) {
-            mIsAnimating = isAnimating;
-        }
-    }
-
-    static TestTask createTestTask(TaskStack stack) {
-        return new TestTask(sNextTaskId++, stack, 0, stack.mWmService, RESIZE_MODE_UNRESIZEABLE,
-                false, mock(TaskRecord.class));
-    }
-
     /** Used to track resize reports. */
     static class TestWindowState extends WindowState {
         boolean mResizeReported;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index af9c510..780fed9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -211,11 +211,7 @@
     ActivityRecord createTestActivityRecord(DisplayContent dc, int
             windowingMode, int activityType) {
         final TaskStack stack = createTaskStackOnDisplay(windowingMode, activityType, dc);
-        final Task task = createTaskInStack(stack, 0 /* userId */);
-        final ActivityRecord activity =
-                WindowTestUtils.createTestActivityRecord(dc);
-        task.addChild(activity, 0);
-        return activity;
+        return WindowTestUtils.createTestActivityRecord(stack.mActivityStack);
     }
 
     WindowState createWindow(WindowState parent, int type, String name) {
@@ -327,14 +323,14 @@
 
     TaskStack createTaskStackOnDisplay(int windowingMode, int activityType, DisplayContent dc) {
         synchronized (mWm.mGlobalLock) {
-            final Configuration overrideConfig = new Configuration();
-            overrideConfig.windowConfiguration.setWindowingMode(windowingMode);
-            overrideConfig.windowConfiguration.setActivityType(activityType);
-            final int stackId = ++sNextStackId;
-            final TaskStack stack = new TaskStack(mWm, stackId, mock(ActivityStack.class));
-            dc.setStackOnDisplay(stackId, true, stack);
-            stack.onRequestedOverrideConfigurationChanged(overrideConfig);
-            return stack;
+            final ActivityStack stack = new ActivityTestsBase.StackBuilder(
+                    dc.mWmService.mAtmService.mRootActivityContainer)
+                    .setDisplay(dc.mActivityDisplay)
+                    .setWindowingMode(windowingMode)
+                    .setActivityType(activityType)
+                    .setCreateActivity(false)
+                    .build();
+            return stack.mTaskStack;
         }
     }
 
@@ -386,4 +382,9 @@
         displayInfo.ownerUid = SYSTEM_UID;
         return createNewDisplay(displayInfo);
     }
+
+    /** Sets the default minimum task size to 1 so that tests can use small task sizes */
+    public void removeGlobalMinSizeRestriction() {
+        mWm.mAtmService.mRootActivityContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/IntervalStats.java b/services/usage/java/com/android/server/usage/IntervalStats.java
index a783a40..46b261b 100644
--- a/services/usage/java/com/android/server/usage/IntervalStats.java
+++ b/services/usage/java/com/android/server/usage/IntervalStats.java
@@ -42,8 +42,12 @@
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.content.res.Configuration;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.proto.ProtoInputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -52,6 +56,8 @@
 import java.util.List;
 
 public class IntervalStats {
+    private static final String TAG = "IntervalStats";
+
     public static final int CURRENT_MAJOR_VERSION = 1;
     public static final int CURRENT_MINOR_VERSION = 1;
     public int majorVersion = CURRENT_MAJOR_VERSION;
@@ -64,6 +70,8 @@
     public final EventTracker keyguardShownTracker = new EventTracker();
     public final EventTracker keyguardHiddenTracker = new EventTracker();
     public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>();
+    /** @hide */
+    public final SparseArray<UsageStats> packageStatsObfuscated = new SparseArray<>();
     public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
     public Configuration activeConfiguration;
     public final EventList events = new EventList();
@@ -436,4 +444,234 @@
         */
         majorVersion = CURRENT_MAJOR_VERSION;
     }
+
+    /**
+     * Parses all of the tokens to strings in the obfuscated usage stats data. This includes
+     * deobfuscating each of the package tokens and chooser actions and categories.
+     */
+    private void deobfuscateUsageStats(PackagesTokenData packagesTokenData) {
+        final int usageStatsSize = packageStatsObfuscated.size();
+        for (int statsIndex = 0; statsIndex < usageStatsSize; statsIndex++) {
+            final int packageToken = packageStatsObfuscated.keyAt(statsIndex);
+            final UsageStats usageStats = packageStatsObfuscated.valueAt(statsIndex);
+            usageStats.mPackageName = packagesTokenData.getPackageString(packageToken);
+            if (usageStats.mPackageName == null) {
+                Slog.e(TAG, "Unable to parse usage stats package " + packageToken);
+                continue;
+            }
+
+            // Update chooser counts
+            final int chooserActionsSize = usageStats.mChooserCountsObfuscated.size();
+            for (int actionIndex = 0; actionIndex < chooserActionsSize; actionIndex++) {
+                final ArrayMap<String, Integer> categoryCountsMap = new ArrayMap<>();
+                final int actionToken = usageStats.mChooserCountsObfuscated.keyAt(actionIndex);
+                final String action = packagesTokenData.getString(packageToken, actionToken);
+                if (action == null) {
+                    Slog.i(TAG, "Unable to parse chooser action " + actionToken
+                            + " for package " + packageToken);
+                    continue;
+                }
+                final SparseIntArray categoryCounts =
+                        usageStats.mChooserCountsObfuscated.valueAt(actionIndex);
+                final int categoriesSize = categoryCounts.size();
+                for (int categoryIndex = 0; categoryIndex < categoriesSize; categoryIndex++) {
+                    final int categoryToken = categoryCounts.keyAt(categoryIndex);
+                    final String category = packagesTokenData.getString(packageToken,
+                            categoryToken);
+                    if (category == null) {
+                        Slog.i(TAG, "Unable to parse chooser category " + categoryToken
+                                + " for package " + packageToken);
+                        continue;
+                    }
+                    categoryCountsMap.put(category, categoryCounts.valueAt(categoryIndex));
+                }
+                usageStats.mChooserCounts.put(action, categoryCountsMap);
+            }
+            packageStats.put(usageStats.mPackageName, usageStats);
+        }
+    }
+
+    /**
+     * Parses all of the tokens to strings in the obfuscated events data. This includes
+     * deobfuscating the package token, along with any class, task root package/class tokens, and
+     * shortcut or notification channel tokens.
+     */
+    private void deobfuscateEvents(PackagesTokenData packagesTokenData) {
+        for (int i = this.events.size() - 1; i >= 0; i--) {
+            final Event event = this.events.get(i);
+            final int packageToken = event.mPackageToken;
+            event.mPackage = packagesTokenData.getPackageString(packageToken);
+            if (event.mPackage == null) {
+                Slog.e(TAG, "Unable to parse event package " + packageToken);
+                this.events.remove(i);
+                continue;
+            }
+
+            if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+                event.mClass = packagesTokenData.getString(packageToken, event.mClassToken);
+                if (event.mClass == null) {
+                    Slog.i(TAG, "Unable to parse class " + event.mClassToken
+                            + " for package " + packageToken);
+                }
+            }
+            if (event.mTaskRootPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+                event.mTaskRootPackage = packagesTokenData.getString(packageToken,
+                        event.mTaskRootPackageToken);
+                if (event.mTaskRootPackage == null) {
+                    Slog.i(TAG, "Unable to parse task root package " + event.mTaskRootPackageToken
+                            + " for package " + packageToken);
+                }
+            }
+            if (event.mTaskRootClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+                event.mTaskRootClass = packagesTokenData.getString(packageToken,
+                        event.mTaskRootClassToken);
+                if (event.mTaskRootClass == null) {
+                    Slog.i(TAG, "Unable to parse task root class " + event.mTaskRootClassToken
+                            + " for package " + packageToken);
+                }
+            }
+            switch (event.mEventType) {
+                case CONFIGURATION_CHANGE:
+                    if (event.mConfiguration == null) {
+                        event.mConfiguration = new Configuration();
+                    }
+                    break;
+                case SHORTCUT_INVOCATION:
+                    event.mShortcutId = packagesTokenData.getString(packageToken,
+                            event.mShortcutIdToken);
+                    if (event.mShortcutId == null) {
+                        Slog.e(TAG, "Unable to parse shortcut " + event.mShortcutIdToken
+                                + " for package " + packageToken);
+                        this.events.remove(i);
+                        continue;
+                    }
+                    break;
+                case NOTIFICATION_INTERRUPTION:
+                    event.mNotificationChannelId = packagesTokenData.getString(packageToken,
+                            event.mNotificationChannelIdToken);
+                    if (event.mNotificationChannelId == null) {
+                        Slog.e(TAG, "Unable to parse notification channel "
+                                + event.mNotificationChannelIdToken + " for package "
+                                + packageToken);
+                        this.events.remove(i);
+                        continue;
+                    }
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Parses the obfuscated tokenized data held in this interval stats object.
+     *
+     * @hide
+     */
+    public void deobfuscateData(PackagesTokenData packagesTokenData) {
+        deobfuscateUsageStats(packagesTokenData);
+        deobfuscateEvents(packagesTokenData);
+    }
+
+    /**
+     * Obfuscates certain strings within each package stats such as the package name, and the
+     * chooser actions and categories.
+     */
+    private void obfuscateUsageStatsData(PackagesTokenData packagesTokenData) {
+        final int usageStatsSize = packageStats.size();
+        for (int statsIndex = 0; statsIndex < usageStatsSize; statsIndex++) {
+            final String packageName = packageStats.keyAt(statsIndex);
+            final UsageStats usageStats = packageStats.valueAt(statsIndex);
+            if (usageStats == null) {
+                continue;
+            }
+
+            final int packageToken = packagesTokenData.getPackageTokenOrAdd(
+                    packageName, usageStats.mEndTimeStamp);
+            // don't obfuscate stats whose packages have been removed
+            if (packageToken == PackagesTokenData.UNASSIGNED_TOKEN) {
+                continue;
+            }
+            usageStats.mPackageToken = packageToken;
+            // Update chooser counts.
+            final int chooserActionsSize = usageStats.mChooserCounts.size();
+            for (int actionIndex = 0; actionIndex < chooserActionsSize; actionIndex++) {
+                final String action = usageStats.mChooserCounts.keyAt(actionIndex);
+                final ArrayMap<String, Integer> categoriesMap =
+                        usageStats.mChooserCounts.valueAt(actionIndex);
+                if (categoriesMap == null) {
+                    continue;
+                }
+
+                final SparseIntArray categoryCounts = new SparseIntArray();
+                final int categoriesSize = categoriesMap.size();
+                for (int categoryIndex = 0; categoryIndex < categoriesSize; categoryIndex++) {
+                    String category = categoriesMap.keyAt(categoryIndex);
+                    int categoryToken = packagesTokenData.getTokenOrAdd(packageToken, packageName,
+                            category);
+                    categoryCounts.put(categoryToken, categoriesMap.valueAt(categoryIndex));
+                }
+                int actionToken = packagesTokenData.getTokenOrAdd(packageToken, packageName,
+                        action);
+                usageStats.mChooserCountsObfuscated.put(actionToken, categoryCounts);
+            }
+            packageStatsObfuscated.put(packageToken, usageStats);
+        }
+    }
+
+    /**
+     * Obfuscates certain strings within an event such as the package name, the class name,
+     * task root package and class names, and shortcut and notification channel ids.
+     */
+    private void obfuscateEventsData(PackagesTokenData packagesTokenData) {
+        for (int i = events.size() - 1; i >= 0; i--) {
+            final Event event = events.get(i);
+            if (event == null) {
+                continue;
+            }
+
+            final int packageToken = packagesTokenData.getPackageTokenOrAdd(
+                    event.mPackage, event.mTimeStamp);
+            // don't obfuscate events from packages that have been removed
+            if (packageToken == PackagesTokenData.UNASSIGNED_TOKEN) {
+                events.remove(i);
+                continue;
+            }
+            event.mPackageToken = packageToken;
+            if (!TextUtils.isEmpty(event.mClass)) {
+                event.mClassToken = packagesTokenData.getTokenOrAdd(packageToken,
+                        event.mPackage, event.mClass);
+            }
+            if (!TextUtils.isEmpty(event.mTaskRootPackage)) {
+                event.mTaskRootPackageToken = packagesTokenData.getTokenOrAdd(packageToken,
+                        event.mPackage, event.mTaskRootPackage);
+            }
+            if (!TextUtils.isEmpty(event.mTaskRootClass)) {
+                event.mTaskRootClassToken = packagesTokenData.getTokenOrAdd(packageToken,
+                        event.mPackage, event.mTaskRootClass);
+            }
+            switch (event.mEventType) {
+                case SHORTCUT_INVOCATION:
+                    if (!TextUtils.isEmpty(event.mShortcutId)) {
+                        event.mShortcutIdToken = packagesTokenData.getTokenOrAdd(packageToken,
+                                event.mPackage, event.mShortcutId);
+                    }
+                    break;
+                case NOTIFICATION_INTERRUPTION:
+                    if (!TextUtils.isEmpty(event.mNotificationChannelId)) {
+                        event.mNotificationChannelIdToken = packagesTokenData.getTokenOrAdd(
+                                packageToken, event.mPackage, event.mNotificationChannelId);
+                    }
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Obfuscates the data in this instance of interval stats.
+     *
+     * @hide
+     */
+    public void obfuscateData(PackagesTokenData packagesTokenData) {
+        obfuscateUsageStatsData(packagesTokenData);
+        obfuscateEventsData(packagesTokenData);
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/PackagesTokenData.java b/services/usage/java/com/android/server/usage/PackagesTokenData.java
new file mode 100644
index 0000000..4bf08a4
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/PackagesTokenData.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usage;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+
+/**
+ * An object holding data defining the obfuscated packages and their token mappings.
+ * Used by {@link UsageStatsDatabase}.
+ *
+ * @hide
+ */
+public final class PackagesTokenData {
+    /**
+     * The package name is always stored at index 0 in {@code tokensToPackagesMap}.
+     */
+    private static final int PACKAGE_NAME_INDEX = 0;
+
+    /**
+     * The default token for any string that hasn't been tokenized yet.
+     */
+    public static final int UNASSIGNED_TOKEN = -1;
+
+    /**
+     * The main token counter for each package.
+     */
+    public int counter = 1;
+    /**
+     * Stores a hierarchy of token to string mappings for each package, indexed by the main
+     * package token. The 0th index within the array list will always hold the package name.
+     */
+    public final SparseArray<ArrayList<String>> tokensToPackagesMap = new SparseArray<>();
+    /**
+     * Stores a hierarchy of strings to token mappings for each package. This is simply an inverse
+     * map of the {@code tokenToPackagesMap} in this class, mainly for an O(1) access to the tokens.
+     */
+    public final ArrayMap<String, ArrayMap<String, Integer>> packagesToTokensMap = new ArrayMap<>();
+    /**
+     * Stores a map of packages that were removed and when they were removed.
+     */
+    public final ArrayMap<String, Long> removedPackagesMap = new ArrayMap<>();
+
+    public PackagesTokenData() {
+    }
+
+    /**
+     * Fetches the token mapped to the given package name. If there is no mapping, a new token is
+     * created and the relevant mappings are updated.
+     *
+     * @param packageName the package name whose token is being fetched
+     * @param timeStamp the time stamp of the event or end time of the usage stats; used to verify
+     *                  the package hasn't been removed
+     * @return the mapped token
+     */
+    public int getPackageTokenOrAdd(String packageName, long timeStamp) {
+        final Long timeRemoved = removedPackagesMap.get(packageName);
+        if (timeRemoved != null && timeRemoved > timeStamp) {
+            return UNASSIGNED_TOKEN; // package was removed
+            /*
+             Note: instead of querying Package Manager each time for a list of packages to verify
+             if this package is still installed, it's more efficient to check the internal list of
+             removed packages and verify with the incoming time stamp. Although rare, it is possible
+             that some asynchronous function is triggered after a package is removed and the
+             time stamp passed into this function is not accurate. We'll have to keep the respective
+             event/usage stat until the next time the device reboots and the mappings are cleaned.
+             Additionally, this is a data class with some helper methods - it doesn't make sense to
+             overload it with references to other services.
+             */
+        }
+
+        ArrayMap<String, Integer> packageTokensMap = packagesToTokensMap.get(packageName);
+        if (packageTokensMap == null) {
+            packageTokensMap = new ArrayMap<>();
+            packagesToTokensMap.put(packageName, packageTokensMap);
+        }
+        int token = packageTokensMap.getOrDefault(packageName, UNASSIGNED_TOKEN);
+        if (token == UNASSIGNED_TOKEN) {
+            token = counter++;
+            // package name should always be at index 0 in the sub-mapping
+            ArrayList<String> tokenPackages = new ArrayList<>();
+            tokenPackages.add(packageName);
+            packageTokensMap.put(packageName, token);
+            tokensToPackagesMap.put(token, tokenPackages);
+        }
+        return token;
+    }
+
+    /**
+     * Fetches the token mapped to the given key within the package's context. If there is no
+     * mapping, a new token is created and the relevant mappings are updated.
+     *
+     * @param packageToken the package token for which the given key belongs to
+     * @param packageName the package name for which the given key belongs to
+     * @param key the key whose token is being fetched
+     * @return the mapped token
+     */
+    public int getTokenOrAdd(int packageToken, String packageName, String key) {
+        if (packageName.equals(key)) {
+            return PACKAGE_NAME_INDEX;
+        }
+        int token = packagesToTokensMap.get(packageName).getOrDefault(key, UNASSIGNED_TOKEN);
+        if (token == UNASSIGNED_TOKEN) {
+            token = tokensToPackagesMap.get(packageToken).size();
+            packagesToTokensMap.get(packageName).put(key, token);
+            tokensToPackagesMap.get(packageToken).add(key);
+        }
+        return token;
+    }
+
+    /**
+     * Fetches the package name for the given token.
+     *
+     * @param packageToken the package token representing the package name
+     * @return the string representing the given token or {@code null} if not found
+     */
+    public String getPackageString(int packageToken) {
+        final ArrayList<String> packageStrings = tokensToPackagesMap.get(packageToken);
+        if (packageStrings == null) {
+            return null;
+        }
+        return packageStrings.get(PACKAGE_NAME_INDEX);
+    }
+
+    /**
+     * Fetches the string represented by the given token.
+     *
+     * @param packageToken the package token for which this token belongs to
+     * @param token the token whose string needs to be fetched
+     * @return the string representing the given token or {@code null} if not found
+     */
+    public String getString(int packageToken, int token) {
+        try {
+            return tokensToPackagesMap.get(packageToken).get(token);
+        } catch (NullPointerException npe) {
+            Slog.e("PackagesTokenData",
+                    "Unable to find tokenized strings for package " + packageToken, npe);
+            return null;
+        } catch (IndexOutOfBoundsException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Removes the package from all known mappings.
+     *
+     * @param packageName the package to be removed
+     * @param timeRemoved the time stamp of when the package was removed
+     */
+    public void removePackage(String packageName, long timeRemoved) {
+        removedPackagesMap.put(packageName, timeRemoved);
+
+        if (!packagesToTokensMap.containsKey(packageName)) {
+            return;
+        }
+        final int packageToken = packagesToTokensMap.get(packageName).get(packageName);
+        packagesToTokensMap.remove(packageName);
+        tokensToPackagesMap.delete(packageToken);
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index 5197b3b..db7ed1f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -17,12 +17,15 @@
 package com.android.server.usage;
 
 import android.app.usage.TimeSparseArray;
+import android.app.usage.UsageEvents;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.os.Build;
 import android.os.SystemProperties;
+import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -75,7 +78,7 @@
  * directory should be deserialized.
  */
 public class UsageStatsDatabase {
-    private static final int DEFAULT_CURRENT_VERSION = 4;
+    private static final int DEFAULT_CURRENT_VERSION = 5;
     /**
      * Current version of the backup schema
      *
@@ -93,7 +96,8 @@
 
     // Persist versioned backup files.
     // Should be false, except when testing new versions
-    static final boolean KEEP_BACKUP_DIR = false;
+    // STOPSHIP: b/139937606 this should be false on launch
+    static final boolean KEEP_BACKUP_DIR = true;
 
     private static final String TAG = "UsageStatsDatabase";
     private static final boolean DEBUG = UsageStatsService.DEBUG;
@@ -119,6 +123,11 @@
     private boolean mFirstUpdate;
     private boolean mNewUpdate;
 
+    // The obfuscated packages to tokens mappings file
+    private final File mPackageMappingsFile;
+    // Holds all of the data related to the obfuscated packages and their token mappings.
+    final PackagesTokenData mPackagesTokenData = new PackagesTokenData();
+
     /**
      * UsageStatsDatabase constructor that allows setting the version number.
      * This should only be used for testing.
@@ -138,6 +147,7 @@
         mBackupsDir = new File(dir, "backups");
         mUpdateBreadcrumb = new File(dir, "breadcrumb");
         mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length];
+        mPackageMappingsFile = new File(dir, "mappings");
         mCal = new UnixCalendar(0);
     }
 
@@ -479,6 +489,11 @@
     private void continueUpgradeLocked(int version, long token) {
         final File backupDir = new File(mBackupsDir, Long.toString(token));
 
+        // Upgrade step logic for the entire usage stats directory, not individual interval dirs.
+        if (version >= 5) {
+            readMappingsLocked();
+        }
+
         // Read each file in the backup according to the version and write to the interval
         // directories in the current versions format
         for (int i = 0; i < mIntervalDirs.length; i++) {
@@ -494,9 +509,16 @@
                     }
                     try {
                         IntervalStats stats = new IntervalStats();
-                        readLocked(new AtomicFile(files[j]), stats, version);
+                        readLocked(new AtomicFile(files[j]), stats, version, mPackagesTokenData);
+                        // Upgrade to version 5+.
+                        // Future version upgrades should add additional logic here to upgrade.
+                        if (mCurrentVersion >= 5) {
+                            // Create the initial obfuscated packages map.
+                            stats.obfuscateData(mPackagesTokenData);
+                        }
                         writeLocked(new AtomicFile(new File(mIntervalDirs[i],
-                                Long.toString(stats.beginTime))), stats, mCurrentVersion);
+                                Long.toString(stats.beginTime))), stats, mCurrentVersion,
+                                mPackagesTokenData);
                     } catch (Exception e) {
                         // This method is called on boot, log the exception and move on
                         Slog.e(TAG, "Failed to upgrade backup file : " + files[j].toString());
@@ -504,6 +526,21 @@
                 }
             }
         }
+
+        // Upgrade step logic for the entire usage stats directory, not individual interval dirs.
+        if (mCurrentVersion >= 5) {
+            try {
+                writeMappingsLocked();
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to write the tokens mappings file.");
+            }
+        }
+    }
+
+    void onPackageRemoved(String packageName, long timeRemoved) {
+        synchronized (mLock) {
+            mPackagesTokenData.removePackage(packageName, timeRemoved);
+        }
     }
 
     public void onTimeChanged(long timeDiffMillis) {
@@ -580,6 +617,37 @@
     }
 
     /**
+     * Filter out those stats from the given stats that belong to removed packages. Filtering out
+     * all of the stats at once has an amortized cost for future calls.
+     */
+    void filterStats(IntervalStats stats) {
+        if (mPackagesTokenData.removedPackagesMap.isEmpty()) {
+            return;
+        }
+        final ArrayMap<String, Long> removedPackagesMap = mPackagesTokenData.removedPackagesMap;
+
+        // filter out package usage stats
+        final int removedPackagesSize = removedPackagesMap.size();
+        for (int i = 0; i < removedPackagesSize; i++) {
+            final String removedPackage = removedPackagesMap.keyAt(i);
+            final UsageStats usageStats = stats.packageStats.get(removedPackage);
+            if (usageStats != null && usageStats.mEndTimeStamp < removedPackagesMap.valueAt(i)) {
+                stats.packageStats.remove(removedPackage);
+            }
+        }
+
+        // filter out events
+        final int eventsSize = stats.events.size();
+        for (int i = stats.events.size() - 1; i >= 0; i--) {
+            final UsageEvents.Event event = stats.events.get(i);
+            final Long timeRemoved = removedPackagesMap.get(event.mPackage);
+            if (timeRemoved != null && timeRemoved > event.mTimeStamp) {
+                stats.events.remove(i);
+            }
+        }
+    }
+
+    /**
      * Figures out what to extract from the given IntervalStats object.
      */
     public interface StatCombiner<T> {
@@ -808,14 +876,14 @@
     }
 
     private void writeLocked(AtomicFile file, IntervalStats stats) throws IOException {
-        writeLocked(file, stats, mCurrentVersion);
+        writeLocked(file, stats, mCurrentVersion, mPackagesTokenData);
     }
 
-    private static void writeLocked(AtomicFile file, IntervalStats stats, int version)
-            throws IOException {
+    private static void writeLocked(AtomicFile file, IntervalStats stats, int version,
+            PackagesTokenData packagesTokenData) throws IOException {
         FileOutputStream fos = file.startWrite();
         try {
-            writeLocked(fos, stats, version);
+            writeLocked(fos, stats, version, packagesTokenData);
             file.finishWrite(fos);
             fos = null;
         } finally {
@@ -825,11 +893,11 @@
     }
 
     private void writeLocked(OutputStream out, IntervalStats stats) throws IOException {
-        writeLocked(out, stats, mCurrentVersion);
+        writeLocked(out, stats, mCurrentVersion, mPackagesTokenData);
     }
 
-    private static void writeLocked(OutputStream out, IntervalStats stats, int version)
-            throws IOException {
+    private static void writeLocked(OutputStream out, IntervalStats stats, int version,
+            PackagesTokenData packagesTokenData) throws IOException {
         switch (version) {
             case 1:
             case 2:
@@ -837,7 +905,19 @@
                 UsageStatsXml.write(out, stats);
                 break;
             case 4:
-                UsageStatsProto.write(out, stats);
+                try {
+                    UsageStatsProto.write(out, stats);
+                } catch (IOException | IllegalArgumentException e) {
+                    Slog.e(TAG, "Unable to write interval stats to proto.", e);
+                }
+                break;
+            case 5:
+                stats.obfuscateData(packagesTokenData);
+                try {
+                    UsageStatsProtoV2.write(out, stats);
+                } catch (IOException | IllegalArgumentException e) {
+                    Slog.e(TAG, "Unable to write interval stats to proto.", e);
+                }
                 break;
             default:
                 throw new RuntimeException(
@@ -847,16 +927,16 @@
     }
 
     private void readLocked(AtomicFile file, IntervalStats statsOut) throws IOException {
-        readLocked(file, statsOut, mCurrentVersion);
+        readLocked(file, statsOut, mCurrentVersion, mPackagesTokenData);
     }
 
-    private static void readLocked(AtomicFile file, IntervalStats statsOut, int version)
-            throws IOException {
+    private static void readLocked(AtomicFile file, IntervalStats statsOut, int version,
+            PackagesTokenData packagesTokenData) throws IOException {
         try {
             FileInputStream in = file.openRead();
             try {
                 statsOut.beginTime = parseBeginTime(file);
-                readLocked(in, statsOut, version);
+                readLocked(in, statsOut, version, packagesTokenData);
                 statsOut.lastTimeSaved = file.getLastModifiedTime();
             } finally {
                 try {
@@ -872,11 +952,11 @@
     }
 
     private void readLocked(InputStream in, IntervalStats statsOut) throws IOException {
-        readLocked(in, statsOut, mCurrentVersion);
+        readLocked(in, statsOut, mCurrentVersion, mPackagesTokenData);
     }
 
-    private static void readLocked(InputStream in, IntervalStats statsOut, int version)
-            throws IOException {
+    private static void readLocked(InputStream in, IntervalStats statsOut, int version,
+            PackagesTokenData packagesTokenData) throws IOException {
         switch (version) {
             case 1:
             case 2:
@@ -884,7 +964,19 @@
                 UsageStatsXml.read(in, statsOut);
                 break;
             case 4:
-                UsageStatsProto.read(in, statsOut);
+                try {
+                    UsageStatsProto.read(in, statsOut);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Unable to read interval stats from proto.", e);
+                }
+                break;
+            case 5:
+                try {
+                    UsageStatsProtoV2.read(in, statsOut);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Unable to read interval stats from proto.", e);
+                }
+                statsOut.deobfuscateData(packagesTokenData);
                 break;
             default:
                 throw new RuntimeException(
@@ -895,6 +987,63 @@
     }
 
     /**
+     * Reads the obfuscated data file from disk containing the tokens to packages mappings and
+     * rebuilds the packages to tokens mappings based on that data.
+     */
+    public void readMappingsLocked() {
+        if (!mPackageMappingsFile.exists()) {
+            return; // package mappings file is missing - recreate mappings on next write.
+        }
+
+        try (FileInputStream in = new AtomicFile(mPackageMappingsFile).openRead()) {
+            UsageStatsProtoV2.readObfuscatedData(in, mPackagesTokenData);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read the obfuscated packages mapping file.", e);
+            return;
+        }
+
+        final SparseArray<ArrayList<String>> tokensToPackagesMap =
+                mPackagesTokenData.tokensToPackagesMap;
+        final int tokensToPackagesMapSize = tokensToPackagesMap.size();
+        for (int i = 0; i < tokensToPackagesMapSize; i++) {
+            final int packageToken = tokensToPackagesMap.keyAt(i);
+            final ArrayList<String> tokensMap = tokensToPackagesMap.valueAt(i);
+            final ArrayMap<String, Integer> packageStringsMap = new ArrayMap<>();
+            final int tokensMapSize = tokensMap.size();
+            // package name will always be at index 0 but its token should not be 0
+            packageStringsMap.put(tokensMap.get(0), packageToken);
+            for (int j = 1; j < tokensMapSize; j++) {
+                packageStringsMap.put(tokensMap.get(j), j);
+            }
+            mPackagesTokenData.packagesToTokensMap.put(tokensMap.get(0), packageStringsMap);
+        }
+    }
+
+    void writeMappingsLocked() throws IOException {
+        final AtomicFile file = new AtomicFile(mPackageMappingsFile);
+        FileOutputStream fos = file.startWrite();
+        try {
+            UsageStatsProtoV2.writeObfuscatedData(fos, mPackagesTokenData);
+            file.finishWrite(fos);
+            fos = null;
+        } catch (IOException | IllegalArgumentException e) {
+            Slog.e(TAG, "Unable to write obfuscated data to proto.", e);
+        } finally {
+            file.failWrite(fos);
+        }
+    }
+
+    void obfuscateCurrentStats(IntervalStats[] currentStats) {
+        if (mCurrentVersion < 5) {
+            return;
+        }
+        for (int i = 0; i < currentStats.length; i++) {
+            final IntervalStats stats = currentStats[i];
+            stats.obfuscateData(mPackagesTokenData);
+        }
+    }
+
+    /**
      * Update the stats in the database. They may not be written to disk immediately.
      */
     public void putUsageStats(int intervalType, IntervalStats stats) throws IOException {
@@ -1098,7 +1247,7 @@
         DataOutputStream out = new DataOutputStream(baos);
         try {
             out.writeLong(stats.beginTime);
-            writeLocked(out, stats, version);
+            writeLocked(out, stats, version, mPackagesTokenData);
         } catch (Exception ioe) {
             Slog.d(TAG, "Serializing IntervalStats Failed", ioe);
             baos.reset();
@@ -1112,7 +1261,7 @@
         IntervalStats stats = new IntervalStats();
         try {
             stats.beginTime = in.readLong();
-            readLocked(in, stats, version);
+            readLocked(in, stats, version, mPackagesTokenData);
         } catch (IOException ioe) {
             Slog.d(TAG, "DeSerializing IntervalStats Failed", ioe);
             stats = null;
@@ -1142,13 +1291,18 @@
     }
 
     /**
-     * print total number and list of stats files for each interval type.
-     * @param pw
+     * Prints the obfuscated package mappings and a summary of the database files.
+     * @param pw the print writer to print to
      */
     public void dump(IndentingPrintWriter pw, boolean compact) {
         synchronized (mLock) {
+            pw.println();
             pw.println("UsageStatsDatabase:");
             pw.increaseIndent();
+            dumpMappings(pw);
+            pw.decreaseIndent();
+            pw.println("Database Summary:");
+            pw.increaseIndent();
             for (int i = 0; i < mSortedStatFiles.length; i++) {
                 final TimeSparseArray<AtomicFile> files = mSortedStatFiles[i];
                 final int size = files.size();
@@ -1173,6 +1327,23 @@
         }
     }
 
+    void dumpMappings(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.println("Obfuscated Packages Mappings:");
+            pw.increaseIndent();
+            pw.println("Counter: " + mPackagesTokenData.counter);
+            pw.println("Tokens Map Size: " + mPackagesTokenData.tokensToPackagesMap.size());
+            for (int i = 0; i < mPackagesTokenData.tokensToPackagesMap.size(); i++) {
+                final int packageToken = mPackagesTokenData.tokensToPackagesMap.keyAt(i);
+                final String packageStrings = String.join(", ",
+                        mPackagesTokenData.tokensToPackagesMap.valueAt(i));
+                pw.println("Token " + packageToken + ": [" + packageStrings + "]");
+            }
+            pw.println();
+            pw.decreaseIndent();
+        }
+    }
+
     IntervalStats readIntervalStatsForFile(int interval, long fileName) {
         synchronized (mLock) {
             final IntervalStats stats = new IntervalStats();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index 6d3f416..5d1f730 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -120,10 +120,14 @@
                             IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT);
                     break;
                 case (int) IntervalStatsProto.UsageStats.CHOOSER_ACTIONS:
-                    final long chooserToken = proto.start(
-                            IntervalStatsProto.UsageStats.CHOOSER_ACTIONS);
-                    loadChooserCounts(proto, stats);
-                    proto.end(chooserToken);
+                    try {
+                        final long chooserToken = proto.start(
+                                IntervalStatsProto.UsageStats.CHOOSER_ACTIONS);
+                        loadChooserCounts(proto, stats);
+                        proto.end(chooserToken);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read chooser counts for " + stats.mPackageName, e);
+                    }
                     break;
                 case (int) IntervalStatsProto.UsageStats.LAST_TIME_SERVICE_USED_MS:
                     // Time attributes stored is an offset of the beginTime.
@@ -153,20 +157,24 @@
     }
 
     private static void loadCountAndTime(ProtoInputStream proto, long fieldId,
-            IntervalStats.EventTracker tracker) throws IOException {
-        final long token = proto.start(fieldId);
-        while (true) {
-            switch (proto.nextField()) {
-                case (int) IntervalStatsProto.CountAndTime.COUNT:
-                    tracker.count = proto.readInt(IntervalStatsProto.CountAndTime.COUNT);
-                    break;
-                case (int) IntervalStatsProto.CountAndTime.TIME_MS:
-                    tracker.duration = proto.readLong(IntervalStatsProto.CountAndTime.TIME_MS);
-                    break;
-                case ProtoInputStream.NO_MORE_FIELDS:
-                    proto.end(token);
-                    return;
+            IntervalStats.EventTracker tracker) {
+        try {
+            final long token = proto.start(fieldId);
+            while (true) {
+                switch (proto.nextField()) {
+                    case (int) IntervalStatsProto.CountAndTime.COUNT:
+                        tracker.count = proto.readInt(IntervalStatsProto.CountAndTime.COUNT);
+                        break;
+                    case (int) IntervalStatsProto.CountAndTime.TIME_MS:
+                        tracker.duration = proto.readLong(IntervalStatsProto.CountAndTime.TIME_MS);
+                        break;
+                    case ProtoInputStream.NO_MORE_FIELDS:
+                        proto.end(token);
+                        return;
+                }
             }
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to read event tracker " + fieldId, e);
         }
     }
 
@@ -306,7 +314,7 @@
     }
 
     private static void writeStringPool(ProtoOutputStream proto, final IntervalStats stats)
-            throws IOException {
+            throws IllegalArgumentException {
         final long token = proto.start(IntervalStatsProto.STRINGPOOL);
         final int size = stats.mStringCache.size();
         proto.write(IntervalStatsProto.StringPool.SIZE, size);
@@ -317,7 +325,8 @@
     }
 
     private static void writeUsageStats(ProtoOutputStream proto, long fieldId,
-            final IntervalStats stats, final UsageStats usageStats) throws IOException {
+            final IntervalStats stats, final UsageStats usageStats)
+            throws IllegalArgumentException {
         final long token = proto.start(fieldId);
         // Write the package name first, so loadUsageStats can avoid creating an extra object
         final int packageIndex = stats.mStringCache.indexOf(usageStats.mPackageName);
@@ -347,12 +356,16 @@
         proto.write(IntervalStatsProto.UsageStats.TOTAL_TIME_VISIBLE_MS,
                 usageStats.mTotalTimeVisible);
         proto.write(IntervalStatsProto.UsageStats.APP_LAUNCH_COUNT, usageStats.mAppLaunchCount);
-        writeChooserCounts(proto, usageStats);
+        try {
+            writeChooserCounts(proto, usageStats);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Unable to write chooser counts for " + usageStats.mPackageName, e);
+        }
         proto.end(token);
     }
 
     private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count,
-            long time) throws IOException {
+            long time) throws IllegalArgumentException {
         final long token = proto.start(fieldId);
         proto.write(IntervalStatsProto.CountAndTime.COUNT, count);
         proto.write(IntervalStatsProto.CountAndTime.TIME_MS, time);
@@ -361,7 +374,7 @@
 
 
     private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats usageStats)
-            throws IOException {
+            throws IllegalArgumentException {
         if (usageStats == null || usageStats.mChooserCounts == null
                 || usageStats.mChooserCounts.keySet().isEmpty()) {
             return;
@@ -381,7 +394,7 @@
     }
 
     private static void writeCountsForAction(ProtoOutputStream proto,
-            ArrayMap<String, Integer> counts) throws IOException {
+            ArrayMap<String, Integer> counts) throws IllegalArgumentException {
         final int countsSize = counts.size();
         for (int i = 0; i < countsSize; i++) {
             String key = counts.keyAt(i);
@@ -397,7 +410,7 @@
 
     private static void writeConfigStats(ProtoOutputStream proto, long fieldId,
             final IntervalStats stats, final ConfigurationStats configStats, boolean isActive)
-            throws IOException {
+            throws IllegalArgumentException {
         final long token = proto.start(fieldId);
         configStats.mConfiguration.writeToProto(proto, IntervalStatsProto.Configuration.CONFIG);
         proto.write(IntervalStatsProto.Configuration.LAST_TIME_ACTIVE_MS,
@@ -407,11 +420,10 @@
         proto.write(IntervalStatsProto.Configuration.COUNT, configStats.mActivationCount);
         proto.write(IntervalStatsProto.Configuration.ACTIVE, isActive);
         proto.end(token);
-
     }
 
     private static void writeEvent(ProtoOutputStream proto, long fieldId, final IntervalStats stats,
-            final UsageEvents.Event event) throws IOException {
+            final UsageEvents.Event event) throws IllegalArgumentException {
         final long token = proto.start(fieldId);
         final int packageIndex = stats.mStringCache.indexOf(event.mPackage);
         if (packageIndex >= 0) {
@@ -542,17 +554,33 @@
                             statsOut.keyguardHiddenTracker);
                     break;
                 case (int) IntervalStatsProto.STRINGPOOL:
-                    stringPool = readStringPool(proto);
-                    statsOut.mStringCache.addAll(stringPool);
+                    try {
+                        stringPool = readStringPool(proto);
+                        statsOut.mStringCache.addAll(stringPool);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read string pool from proto.", e);
+                    }
                     break;
                 case (int) IntervalStatsProto.PACKAGES:
-                    loadUsageStats(proto, IntervalStatsProto.PACKAGES, statsOut, stringPool);
+                    try {
+                        loadUsageStats(proto, IntervalStatsProto.PACKAGES, statsOut, stringPool);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read some usage stats from proto.", e);
+                    }
                     break;
                 case (int) IntervalStatsProto.CONFIGURATIONS:
-                    loadConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, statsOut);
+                    try {
+                        loadConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, statsOut);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read some configuration stats from proto.", e);
+                    }
                     break;
                 case (int) IntervalStatsProto.EVENT_LOG:
-                    loadEvent(proto, IntervalStatsProto.EVENT_LOG, statsOut, stringPool);
+                    try {
+                        loadEvent(proto, IntervalStatsProto.EVENT_LOG, statsOut, stringPool);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read some events from proto.", e);
+                    }
                     break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     if (statsOut.endTime == 0) {
@@ -571,72 +599,60 @@
      * @param proto The serializer to which to write the packageStats data.
      * @param stats The stats object to write to the XML file.
      */
-    public static void write(OutputStream out, IntervalStats stats) throws IOException {
+    public static void write(OutputStream out, IntervalStats stats)
+            throws IOException, IllegalArgumentException {
         final ProtoOutputStream proto = new ProtoOutputStream(out);
         proto.write(IntervalStatsProto.END_TIME_MS, stats.endTime - stats.beginTime);
         proto.write(IntervalStatsProto.MAJOR_VERSION, stats.majorVersion);
         proto.write(IntervalStatsProto.MINOR_VERSION, stats.minorVersion);
         // String pool should be written before the rest of the usage stats
-        writeStringPool(proto, stats);
+        try {
+            writeStringPool(proto, stats);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Unable to write string pool to proto.", e);
+        }
 
-        writeCountAndTime(proto, IntervalStatsProto.INTERACTIVE, stats.interactiveTracker.count,
-                stats.interactiveTracker.duration);
-        writeCountAndTime(proto, IntervalStatsProto.NON_INTERACTIVE,
-                stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
-        writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_SHOWN,
-                stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
-        writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_HIDDEN,
-                stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
+        try {
+            writeCountAndTime(proto, IntervalStatsProto.INTERACTIVE, stats.interactiveTracker.count,
+                    stats.interactiveTracker.duration);
+            writeCountAndTime(proto, IntervalStatsProto.NON_INTERACTIVE,
+                    stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
+            writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_SHOWN,
+                    stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
+            writeCountAndTime(proto, IntervalStatsProto.KEYGUARD_HIDDEN,
+                    stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
+        }  catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Unable to write some interval stats trackers to proto.", e);
+        }
 
         final int statsCount = stats.packageStats.size();
         for (int i = 0; i < statsCount; i++) {
-            writeUsageStats(proto, IntervalStatsProto.PACKAGES, stats,
-                    stats.packageStats.valueAt(i));
+            try {
+                writeUsageStats(proto, IntervalStatsProto.PACKAGES, stats,
+                        stats.packageStats.valueAt(i));
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Unable to write some usage stats to proto.", e);
+            }
         }
         final int configCount = stats.configurations.size();
         for (int i = 0; i < configCount; i++) {
             boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
-            writeConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, stats,
-                    stats.configurations.valueAt(i), active);
+            try {
+                writeConfigStats(proto, IntervalStatsProto.CONFIGURATIONS, stats,
+                        stats.configurations.valueAt(i), active);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Unable to write some configuration stats to proto.", e);
+            }
         }
         final int eventCount = stats.events.size();
         for (int i = 0; i < eventCount; i++) {
-            writeEvent(proto, IntervalStatsProto.EVENT_LOG, stats, stats.events.get(i));
-        }
-
-        proto.flush();
-    }
-
-    // TODO: move to UsageStatsProtoV2
-    static void readPendingEvents(InputStream in, List<UsageEvents.Event> events)
-            throws IOException {
-        final ProtoInputStream proto = new ProtoInputStream(in);
-        final List<String> stringPool = new ArrayList<>();
-        final IntervalStats tmpStatsObj = new IntervalStats();
-        while (true) {
-            switch (proto.nextField()) {
-                case (int) IntervalStatsProto.PENDING_EVENTS:
-                    loadEvent(proto, IntervalStatsProto.PENDING_EVENTS, tmpStatsObj, stringPool);
-                    break;
-                case ProtoInputStream.NO_MORE_FIELDS:
-                    final int eventCount = tmpStatsObj.events.size();
-                    for (int i = 0; i < eventCount; i++) {
-                        events.add(tmpStatsObj.events.get(i));
-                    }
-                    return;
+            try {
+                writeEvent(proto, IntervalStatsProto.EVENT_LOG, stats, stats.events.get(i));
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Unable to write some events to proto.", e);
             }
         }
-    }
 
-    // TODO: move to UsageStatsProtoV2
-    static void writePendingEvents(OutputStream out, List<UsageEvents.Event> events)
-            throws IOException {
-        final ProtoOutputStream proto = new ProtoOutputStream(out);
-        final IntervalStats tmpStatsObj = new IntervalStats();
-        final int eventCount = events.size();
-        for (int i = 0; i < eventCount; i++) {
-            writeEvent(proto, IntervalStatsProto.PENDING_EVENTS, tmpStatsObj, events.get(i));
-        }
         proto.flush();
     }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
new file mode 100644
index 0000000..7d8e430
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsProtoV2.java
@@ -0,0 +1,788 @@
+/*
+ * 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.usage;
+
+import android.app.usage.ConfigurationStats;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStats;
+import android.content.res.Configuration;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * UsageStats reader/writer V2 for Protocol Buffer format.
+ */
+final class UsageStatsProtoV2 {
+    private static final String TAG = "UsageStatsProtoV2";
+
+    // Static-only utility class.
+    private UsageStatsProtoV2() {}
+
+    private static UsageStats parseUsageStats(ProtoInputStream proto, final long beginTime)
+            throws IOException {
+        UsageStats stats = new UsageStats();
+        // Time attributes stored is an offset of the beginTime.
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) UsageStatsObfuscatedProto.PACKAGE_TOKEN:
+                    stats.mPackageToken = proto.readInt(
+                            UsageStatsObfuscatedProto.PACKAGE_TOKEN) - 1;
+                    break;
+                case (int) UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS:
+                    stats.mLastTimeUsed = beginTime + proto.readLong(
+                            UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS);
+                    break;
+                case (int) UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS:
+                    stats.mTotalTimeInForeground = proto.readLong(
+                            UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS);
+                    break;
+                case (int) UsageStatsObfuscatedProto.APP_LAUNCH_COUNT:
+                    stats.mAppLaunchCount = proto.readInt(
+                            UsageStatsObfuscatedProto.APP_LAUNCH_COUNT);
+                    break;
+                case (int) UsageStatsObfuscatedProto.CHOOSER_ACTIONS:
+                    try {
+                        final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS);
+                        loadChooserCounts(proto, stats);
+                        proto.end(token);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read chooser counts for " + stats.mPackageToken);
+                    }
+                    break;
+                case (int) UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS:
+                    stats.mLastTimeForegroundServiceUsed = beginTime + proto.readLong(
+                            UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS);
+                    break;
+                case (int) UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS:
+                    stats.mTotalTimeForegroundServiceUsed = proto.readLong(
+                            UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS);
+                    break;
+                case (int) UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS:
+                    stats.mLastTimeVisible = beginTime + proto.readLong(
+                            UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS);
+                    break;
+                case (int) UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS:
+                    stats.mTotalTimeVisible = proto.readLong(
+                            UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    // mLastTimeUsed was not read, assume default value of 0 plus beginTime
+                    if (stats.mLastTimeUsed == 0) {
+                        stats.mLastTimeUsed = beginTime;
+                    }
+                    return stats;
+            }
+        }
+    }
+
+    private static void loadCountAndTime(ProtoInputStream proto, long fieldId,
+            IntervalStats.EventTracker tracker) {
+        try {
+            final long token = proto.start(fieldId);
+            while (true) {
+                switch (proto.nextField()) {
+                    case (int) IntervalStatsObfuscatedProto.CountAndTime.COUNT:
+                        tracker.count = proto.readInt(
+                                IntervalStatsObfuscatedProto.CountAndTime.COUNT);
+                        break;
+                    case (int) IntervalStatsObfuscatedProto.CountAndTime.TIME_MS:
+                        tracker.duration = proto.readLong(
+                                IntervalStatsObfuscatedProto.CountAndTime.TIME_MS);
+                        break;
+                    case ProtoInputStream.NO_MORE_FIELDS:
+                        proto.end(token);
+                        return;
+                }
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to read event tracker " + fieldId, e);
+        }
+    }
+
+    private static void loadChooserCounts(ProtoInputStream proto, UsageStats usageStats)
+            throws IOException {
+        int actionToken;
+        SparseIntArray counts;
+        if (proto.nextField(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN)) {
+            // Fast path; this should work for most cases since the action token is written first
+            actionToken = proto.readInt(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1;
+            counts = usageStats.mChooserCountsObfuscated.get(actionToken);
+            if (counts == null) {
+                counts = new SparseIntArray();
+                usageStats.mChooserCountsObfuscated.put(actionToken, counts);
+            }
+        } else {
+            counts = new SparseIntArray();
+        }
+
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN:
+                    // Fast path failed for some reason, add the SparseIntArray object to usageStats
+                    actionToken = proto.readInt(
+                            UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN) - 1;
+                    usageStats.mChooserCountsObfuscated.put(actionToken, counts);
+                    break;
+                case (int) UsageStatsObfuscatedProto.ChooserAction.COUNTS:
+                    final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS);
+                    loadCountsForAction(proto, counts);
+                    proto.end(token);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    return; // if the action was never read, the loaded counts will be ignored.
+            }
+        }
+    }
+
+    private static void loadCountsForAction(ProtoInputStream proto, SparseIntArray counts)
+            throws IOException {
+        int categoryToken = PackagesTokenData.UNASSIGNED_TOKEN;
+        int count = 0;
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN:
+                    categoryToken = proto.readInt(
+                            UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN)
+                            - 1;
+                    break;
+                case (int) UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT:
+                    count = proto.readInt(
+                            UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    if (categoryToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+                        counts.put(categoryToken, count);
+                    }
+                    return;
+            }
+        }
+    }
+
+    private static void loadConfigStats(ProtoInputStream proto, IntervalStats stats)
+            throws IOException {
+        boolean configActive = false;
+        final Configuration config = new Configuration();
+        ConfigurationStats configStats = new ConfigurationStats();
+        if (proto.nextField(IntervalStatsObfuscatedProto.Configuration.CONFIG)) {
+            // Fast path; this should work since the configuration is written first
+            config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG);
+            configStats = stats.getOrCreateConfigurationStats(config);
+        }
+
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) IntervalStatsObfuscatedProto.Configuration.CONFIG:
+                    // Fast path failed from some reason, add ConfigStats object to statsOut now
+                    config.readFromProto(proto, IntervalStatsObfuscatedProto.Configuration.CONFIG);
+                    final ConfigurationStats temp = stats.getOrCreateConfigurationStats(config);
+                    temp.mLastTimeActive = configStats.mLastTimeActive;
+                    temp.mTotalTimeActive = configStats.mTotalTimeActive;
+                    temp.mActivationCount = configStats.mActivationCount;
+                    configStats = temp;
+                    break;
+                case (int) IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS:
+                    configStats.mLastTimeActive = stats.beginTime + proto.readLong(
+                            IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS:
+                    configStats.mTotalTimeActive = proto.readLong(
+                            IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.Configuration.COUNT:
+                    configStats.mActivationCount = proto.readInt(
+                            IntervalStatsObfuscatedProto.Configuration.COUNT);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.Configuration.ACTIVE:
+                    configActive = proto.readBoolean(
+                            IntervalStatsObfuscatedProto.Configuration.ACTIVE);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    // mLastTimeActive was not assigned, assume default value of 0 plus beginTime
+                    if (configStats.mLastTimeActive == 0) {
+                        configStats.mLastTimeActive = stats.beginTime;
+                    }
+                    if (configActive) {
+                        stats.activeConfiguration = configStats.mConfiguration;
+                    }
+                    return;
+            }
+        }
+    }
+
+    private static UsageEvents.Event parseEvent(ProtoInputStream proto, long beginTime)
+            throws IOException {
+        final UsageEvents.Event event = new UsageEvents.Event();
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) EventObfuscatedProto.PACKAGE_TOKEN:
+                    event.mPackageToken = proto.readInt(EventObfuscatedProto.PACKAGE_TOKEN) - 1;
+                    break;
+                case (int) EventObfuscatedProto.CLASS_TOKEN:
+                    event.mClassToken = proto.readInt(EventObfuscatedProto.CLASS_TOKEN) - 1;
+                    break;
+                case (int) EventObfuscatedProto.TIME_MS:
+                    event.mTimeStamp = beginTime + proto.readLong(EventObfuscatedProto.TIME_MS);
+                    break;
+                case (int) EventObfuscatedProto.FLAGS:
+                    event.mFlags = proto.readInt(EventObfuscatedProto.FLAGS);
+                    break;
+                case (int) EventObfuscatedProto.TYPE:
+                    event.mEventType = proto.readInt(EventObfuscatedProto.TYPE);
+                    break;
+                case (int) EventObfuscatedProto.CONFIG:
+                    event.mConfiguration = new Configuration();
+                    event.mConfiguration.readFromProto(proto, EventObfuscatedProto.CONFIG);
+                    break;
+                case (int) EventObfuscatedProto.SHORTCUT_ID_TOKEN:
+                    event.mShortcutIdToken = proto.readInt(
+                            EventObfuscatedProto.SHORTCUT_ID_TOKEN) - 1;
+                    break;
+                case (int) EventObfuscatedProto.STANDBY_BUCKET:
+                    event.mBucketAndReason = proto.readInt(EventObfuscatedProto.STANDBY_BUCKET);
+                    break;
+                case (int) EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN:
+                    event.mNotificationChannelIdToken = proto.readInt(
+                            EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN) - 1;
+                    break;
+                case (int) EventObfuscatedProto.INSTANCE_ID:
+                    event.mInstanceId = proto.readInt(EventObfuscatedProto.INSTANCE_ID);
+                    break;
+                case (int) EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN:
+                    event.mTaskRootPackageToken = proto.readInt(
+                            EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN) - 1;
+                    break;
+                case (int) EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN:
+                    event.mTaskRootClassToken = proto.readInt(
+                            EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN) - 1;
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    // timeStamp was not read, assume default value 0 plus beginTime
+                    if (event.mTimeStamp == 0) {
+                        event.mTimeStamp = beginTime;
+                    }
+                    return event.mPackageToken == PackagesTokenData.UNASSIGNED_TOKEN ? null : event;
+            }
+        }
+    }
+
+    private static void writeUsageStats(ProtoOutputStream proto, final long beginTime,
+            final UsageStats stats) throws IllegalArgumentException {
+        // Time attributes stored as an offset of the beginTime.
+        proto.write(UsageStatsObfuscatedProto.PACKAGE_TOKEN, stats.mPackageToken + 1);
+        proto.write(UsageStatsObfuscatedProto.LAST_TIME_ACTIVE_MS, stats.mLastTimeUsed - beginTime);
+        proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_ACTIVE_MS, stats.mTotalTimeInForeground);
+        proto.write(UsageStatsObfuscatedProto.LAST_TIME_SERVICE_USED_MS,
+                stats.mLastTimeForegroundServiceUsed - beginTime);
+        proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_SERVICE_USED_MS,
+                stats.mTotalTimeForegroundServiceUsed);
+        proto.write(UsageStatsObfuscatedProto.LAST_TIME_VISIBLE_MS,
+                stats.mLastTimeVisible - beginTime);
+        proto.write(UsageStatsObfuscatedProto.TOTAL_TIME_VISIBLE_MS, stats.mTotalTimeVisible);
+        proto.write(UsageStatsObfuscatedProto.APP_LAUNCH_COUNT, stats.mAppLaunchCount);
+        try {
+            writeChooserCounts(proto, stats);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Unable to write chooser counts for " + stats.mPackageName, e);
+        }
+    }
+
+    private static void writeCountAndTime(ProtoOutputStream proto, long fieldId, int count,
+            long time) throws IllegalArgumentException {
+        final long token = proto.start(fieldId);
+        proto.write(IntervalStatsObfuscatedProto.CountAndTime.COUNT, count);
+        proto.write(IntervalStatsObfuscatedProto.CountAndTime.TIME_MS, time);
+        proto.end(token);
+    }
+
+    private static void writeChooserCounts(ProtoOutputStream proto, final UsageStats stats)
+            throws IllegalArgumentException {
+        if (stats == null || stats.mChooserCountsObfuscated.size() == 0) {
+            return;
+        }
+        final int chooserCountSize = stats.mChooserCountsObfuscated.size();
+        for (int i = 0; i < chooserCountSize; i++) {
+            final int action = stats.mChooserCountsObfuscated.keyAt(i);
+            final SparseIntArray counts = stats.mChooserCountsObfuscated.valueAt(i);
+            if (counts == null || counts.size() == 0) {
+                continue;
+            }
+            final long token = proto.start(UsageStatsObfuscatedProto.CHOOSER_ACTIONS);
+            proto.write(UsageStatsObfuscatedProto.ChooserAction.ACTION_TOKEN, action + 1);
+            writeCountsForAction(proto, counts);
+            proto.end(token);
+        }
+    }
+
+    private static void writeCountsForAction(ProtoOutputStream proto, SparseIntArray counts)
+            throws IllegalArgumentException {
+        final int countsSize = counts.size();
+        for (int i = 0; i < countsSize; i++) {
+            final int category = counts.keyAt(i);
+            final int count = counts.valueAt(i);
+            if (count <= 0) {
+                continue;
+            }
+            final long token = proto.start(UsageStatsObfuscatedProto.ChooserAction.COUNTS);
+            proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.CATEGORY_TOKEN,
+                    category + 1);
+            proto.write(UsageStatsObfuscatedProto.ChooserAction.CategoryCount.COUNT, count);
+            proto.end(token);
+        }
+    }
+
+    private static void writeConfigStats(ProtoOutputStream proto, final long statsBeginTime,
+            final ConfigurationStats configStats, boolean isActive)
+            throws IllegalArgumentException {
+        configStats.mConfiguration.writeToProto(proto,
+                IntervalStatsObfuscatedProto.Configuration.CONFIG);
+        proto.write(IntervalStatsObfuscatedProto.Configuration.LAST_TIME_ACTIVE_MS,
+                configStats.mLastTimeActive - statsBeginTime);
+        proto.write(IntervalStatsObfuscatedProto.Configuration.TOTAL_TIME_ACTIVE_MS,
+                configStats.mTotalTimeActive);
+        proto.write(IntervalStatsObfuscatedProto.Configuration.COUNT, configStats.mActivationCount);
+        proto.write(IntervalStatsObfuscatedProto.Configuration.ACTIVE, isActive);
+    }
+
+    private static void writeEvent(ProtoOutputStream proto, final long statsBeginTime,
+            final UsageEvents.Event event) throws IllegalArgumentException {
+        proto.write(EventObfuscatedProto.PACKAGE_TOKEN, event.mPackageToken + 1);
+        if (event.mClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+            proto.write(EventObfuscatedProto.CLASS_TOKEN, event.mClassToken + 1);
+        }
+        proto.write(EventObfuscatedProto.TIME_MS, event.mTimeStamp - statsBeginTime);
+        proto.write(EventObfuscatedProto.FLAGS, event.mFlags);
+        proto.write(EventObfuscatedProto.TYPE, event.mEventType);
+        proto.write(EventObfuscatedProto.INSTANCE_ID, event.mInstanceId);
+        if (event.mTaskRootPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+            proto.write(EventObfuscatedProto.TASK_ROOT_PACKAGE_TOKEN,
+                    event.mTaskRootPackageToken + 1);
+        }
+        if (event.mTaskRootClassToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+            proto.write(EventObfuscatedProto.TASK_ROOT_CLASS_TOKEN, event.mTaskRootClassToken + 1);
+        }
+        switch (event.mEventType) {
+            case UsageEvents.Event.CONFIGURATION_CHANGE:
+                if (event.mConfiguration != null) {
+                    event.mConfiguration.writeToProto(proto, EventObfuscatedProto.CONFIG);
+                }
+                break;
+            case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
+                if (event.mBucketAndReason != 0) {
+                    proto.write(EventObfuscatedProto.STANDBY_BUCKET, event.mBucketAndReason);
+                }
+                break;
+            case UsageEvents.Event.SHORTCUT_INVOCATION:
+                if (event.mShortcutIdToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+                    proto.write(EventObfuscatedProto.SHORTCUT_ID_TOKEN, event.mShortcutIdToken + 1);
+                }
+                break;
+            case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                if (event.mNotificationChannelIdToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+                    proto.write(EventObfuscatedProto.NOTIFICATION_CHANNEL_ID_TOKEN,
+                            event.mNotificationChannelIdToken + 1);
+                }
+                break;
+        }
+    }
+
+    /**
+     * Populates a tokenized version of interval stats from the input stream given.
+     *
+     * @param in the input stream from which to read events.
+     * @param stats the interval stats object which will be populated.
+     */
+    public static void read(InputStream in, IntervalStats stats) throws IOException {
+        final ProtoInputStream proto = new ProtoInputStream(in);
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) IntervalStatsObfuscatedProto.END_TIME_MS:
+                    stats.endTime = stats.beginTime + proto.readLong(
+                            IntervalStatsObfuscatedProto.END_TIME_MS);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.MAJOR_VERSION:
+                    stats.majorVersion = proto.readInt(IntervalStatsObfuscatedProto.MAJOR_VERSION);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.MINOR_VERSION:
+                    stats.minorVersion = proto.readInt(IntervalStatsObfuscatedProto.MINOR_VERSION);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.INTERACTIVE:
+                    loadCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE,
+                            stats.interactiveTracker);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.NON_INTERACTIVE:
+                    loadCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE,
+                            stats.nonInteractiveTracker);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.KEYGUARD_SHOWN:
+                    loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN,
+                            stats.keyguardShownTracker);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN:
+                    loadCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN,
+                            stats.keyguardHiddenTracker);
+                    break;
+                case (int) IntervalStatsObfuscatedProto.PACKAGES:
+                    try {
+                        final long packagesToken = proto.start(
+                                IntervalStatsObfuscatedProto.PACKAGES);
+                        UsageStats usageStats = parseUsageStats(proto, stats.beginTime);
+                        if (usageStats.mPackageToken != PackagesTokenData.UNASSIGNED_TOKEN) {
+                            stats.packageStatsObfuscated.put(usageStats.mPackageToken, usageStats);
+                        }
+                        proto.end(packagesToken);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read some usage stats from proto.", e);
+                    }
+                    break;
+                case (int) IntervalStatsObfuscatedProto.CONFIGURATIONS:
+                    try {
+                        final long configsToken = proto.start(
+                                IntervalStatsObfuscatedProto.CONFIGURATIONS);
+                        loadConfigStats(proto, stats);
+                        proto.end(configsToken);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read some configuration stats from proto.", e);
+                    }
+                    break;
+                case (int) IntervalStatsObfuscatedProto.EVENT_LOG:
+                    try {
+                        final long eventsToken = proto.start(
+                                IntervalStatsObfuscatedProto.EVENT_LOG);
+                        UsageEvents.Event event = parseEvent(proto, stats.beginTime);
+                        proto.end(eventsToken);
+                        if (event != null) {
+                            stats.events.insert(event);
+                        }
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to read some events from proto.", e);
+                    }
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    // endTime not assigned, assume default value of 0 plus beginTime
+                    if (stats.endTime == 0) {
+                        stats.endTime = stats.beginTime;
+                    }
+                    return;
+            }
+        }
+    }
+
+    /**
+     * Writes the tokenized interval stats object to a ProtoBuf file.
+     *
+     * @param out the output stream to which to write the interval stats data.
+     * @param stats the interval stats object to write to the proto file.
+     */
+    public static void write(OutputStream out, IntervalStats stats)
+            throws IOException, IllegalArgumentException {
+        final ProtoOutputStream proto = new ProtoOutputStream(out);
+        proto.write(IntervalStatsObfuscatedProto.END_TIME_MS, stats.endTime - stats.beginTime);
+        proto.write(IntervalStatsObfuscatedProto.MAJOR_VERSION, stats.majorVersion);
+        proto.write(IntervalStatsObfuscatedProto.MINOR_VERSION, stats.minorVersion);
+
+        try {
+            writeCountAndTime(proto, IntervalStatsObfuscatedProto.INTERACTIVE,
+                    stats.interactiveTracker.count, stats.interactiveTracker.duration);
+            writeCountAndTime(proto, IntervalStatsObfuscatedProto.NON_INTERACTIVE,
+                    stats.nonInteractiveTracker.count, stats.nonInteractiveTracker.duration);
+            writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_SHOWN,
+                    stats.keyguardShownTracker.count, stats.keyguardShownTracker.duration);
+            writeCountAndTime(proto, IntervalStatsObfuscatedProto.KEYGUARD_HIDDEN,
+                    stats.keyguardHiddenTracker.count, stats.keyguardHiddenTracker.duration);
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Unable to write some interval stats trackers to proto.", e);
+        }
+
+        final int statsCount = stats.packageStatsObfuscated.size();
+        for (int i = 0; i < statsCount; i++) {
+            try {
+                final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGES);
+                writeUsageStats(proto, stats.beginTime, stats.packageStatsObfuscated.valueAt(i));
+                proto.end(token);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Unable to write some usage stats to proto.", e);
+            }
+        }
+        final int configCount = stats.configurations.size();
+        for (int i = 0; i < configCount; i++) {
+            boolean active = stats.activeConfiguration.equals(stats.configurations.keyAt(i));
+            try {
+                final long token = proto.start(IntervalStatsObfuscatedProto.CONFIGURATIONS);
+                writeConfigStats(proto, stats.beginTime, stats.configurations.valueAt(i), active);
+                proto.end(token);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Unable to write some configuration stats to proto.", e);
+            }
+        }
+        final int eventCount = stats.events.size();
+        for (int i = 0; i < eventCount; i++) {
+            try {
+                final long token = proto.start(IntervalStatsObfuscatedProto.EVENT_LOG);
+                writeEvent(proto, stats.beginTime, stats.events.get(i));
+                proto.end(token);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Unable to write some events to proto.", e);
+            }
+        }
+
+        proto.flush();
+    }
+
+    /***** Read/Write obfuscated packages data logic. *****/
+
+    private static void loadPackagesMap(ProtoInputStream proto,
+            SparseArray<ArrayList<String>> tokensToPackagesMap) throws IOException {
+        int key = PackagesTokenData.UNASSIGNED_TOKEN;
+        final ArrayList<String> strings = new ArrayList<>();
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN:
+                    key = proto.readInt(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN) - 1;
+                    break;
+                case (int) ObfuscatedPackagesProto.PackagesMap.STRINGS:
+                    strings.add(proto.readString(ObfuscatedPackagesProto.PackagesMap.STRINGS));
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    if (key != PackagesTokenData.UNASSIGNED_TOKEN) {
+                        tokensToPackagesMap.put(key, strings);
+                    }
+                    return;
+            }
+        }
+    }
+
+    /**
+     * Populates the package mappings from the input stream given.
+     *
+     * @param in the input stream from which to read the mappings.
+     * @param packagesTokenData the packages data object to which the data will be read to.
+     */
+    static void readObfuscatedData(InputStream in, PackagesTokenData packagesTokenData)
+            throws IOException {
+        final ProtoInputStream proto = new ProtoInputStream(in);
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) ObfuscatedPackagesProto.COUNTER:
+                    packagesTokenData.counter = proto.readInt(ObfuscatedPackagesProto.COUNTER);
+                    break;
+                case (int) ObfuscatedPackagesProto.PACKAGES_MAP:
+                    final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP);
+                    loadPackagesMap(proto, packagesTokenData.tokensToPackagesMap);
+                    proto.end(token);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    return;
+            }
+        }
+    }
+
+    /**
+     * Writes the packages mapping data to a ProtoBuf file.
+     *
+     * @param out the output stream to which to write the mappings.
+     * @param packagesTokenData the packages data object holding the data to write.
+     */
+    static void writeObfuscatedData(OutputStream out, PackagesTokenData packagesTokenData)
+            throws IOException, IllegalArgumentException {
+        final ProtoOutputStream proto = new ProtoOutputStream(out);
+        proto.write(ObfuscatedPackagesProto.COUNTER, packagesTokenData.counter);
+
+        final int mapSize = packagesTokenData.tokensToPackagesMap.size();
+        for (int i = 0; i < mapSize; i++) {
+            final long token = proto.start(ObfuscatedPackagesProto.PACKAGES_MAP);
+            int packageToken = packagesTokenData.tokensToPackagesMap.keyAt(i);
+            proto.write(ObfuscatedPackagesProto.PackagesMap.PACKAGE_TOKEN, packageToken + 1);
+
+            final ArrayList<String> strings = packagesTokenData.tokensToPackagesMap.valueAt(i);
+            final int listSize = strings.size();
+            for (int j = 0; j < listSize; j++) {
+                proto.write(ObfuscatedPackagesProto.PackagesMap.STRINGS, strings.get(j));
+            }
+            proto.end(token);
+        }
+
+        proto.flush();
+    }
+
+    /***** Read/Write pending events logic. *****/
+
+    private static UsageEvents.Event parsePendingEvent(ProtoInputStream proto) throws IOException {
+        final UsageEvents.Event event = new UsageEvents.Event();
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) PendingEventProto.PACKAGE_NAME:
+                    event.mPackage = proto.readString(PendingEventProto.PACKAGE_NAME);
+                    break;
+                case (int) PendingEventProto.CLASS_NAME:
+                    event.mClass = proto.readString(PendingEventProto.CLASS_NAME);
+                    break;
+                case (int) PendingEventProto.TIME_MS:
+                    event.mTimeStamp = proto.readLong(PendingEventProto.TIME_MS);
+                    break;
+                case (int) PendingEventProto.FLAGS:
+                    event.mFlags = proto.readInt(PendingEventProto.FLAGS);
+                    break;
+                case (int) PendingEventProto.TYPE:
+                    event.mEventType = proto.readInt(PendingEventProto.TYPE);
+                    break;
+                case (int) PendingEventProto.CONFIG:
+                    event.mConfiguration = new Configuration();
+                    event.mConfiguration.readFromProto(proto, PendingEventProto.CONFIG);
+                    break;
+                case (int) PendingEventProto.SHORTCUT_ID:
+                    event.mShortcutId = proto.readString(PendingEventProto.SHORTCUT_ID);
+                    break;
+                case (int) PendingEventProto.STANDBY_BUCKET:
+                    event.mBucketAndReason = proto.readInt(PendingEventProto.STANDBY_BUCKET);
+                    break;
+                case (int) PendingEventProto.NOTIFICATION_CHANNEL_ID:
+                    event.mNotificationChannelId = proto.readString(
+                            PendingEventProto.NOTIFICATION_CHANNEL_ID);
+                    break;
+                case (int) PendingEventProto.INSTANCE_ID:
+                    event.mInstanceId = proto.readInt(PendingEventProto.INSTANCE_ID);
+                    break;
+                case (int) PendingEventProto.TASK_ROOT_PACKAGE:
+                    event.mTaskRootPackage = proto.readString(PendingEventProto.TASK_ROOT_PACKAGE);
+                    break;
+                case (int) PendingEventProto.TASK_ROOT_CLASS:
+                    event.mTaskRootClass = proto.readString(PendingEventProto.TASK_ROOT_CLASS);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    // Handle default values for certain events types
+                    switch (event.mEventType) {
+                        case UsageEvents.Event.CONFIGURATION_CHANGE:
+                            if (event.mConfiguration == null) {
+                                event.mConfiguration = new Configuration();
+                            }
+                            break;
+                        case UsageEvents.Event.SHORTCUT_INVOCATION:
+                            if (event.mShortcutId == null) {
+                                event.mShortcutId = "";
+                            }
+                            break;
+                        case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                            if (event.mNotificationChannelId == null) {
+                                event.mNotificationChannelId = "";
+                            }
+                            break;
+                    }
+                    return event.mPackage == null ? null : event;
+            }
+        }
+    }
+
+    /**
+     * Populates the list of pending events from the input stream given.
+     *
+     * @param in the input stream from which to read the pending events.
+     * @param events the list of pending events to populate.
+     */
+    static void readPendingEvents(InputStream in, LinkedList<UsageEvents.Event> events)
+            throws IOException {
+        final ProtoInputStream proto = new ProtoInputStream(in);
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) IntervalStatsObfuscatedProto.PENDING_EVENTS:
+                    try {
+                        final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
+                        UsageEvents.Event event = parsePendingEvent(proto);
+                        proto.end(token);
+                        if (event != null) {
+                            events.add(event);
+                        }
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Unable to parse some pending events from proto.", e);
+                    }
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    return;
+            }
+        }
+    }
+
+    private static void writePendingEvent(ProtoOutputStream proto, UsageEvents.Event event)
+            throws IllegalArgumentException {
+        proto.write(PendingEventProto.PACKAGE_NAME, event.mPackage);
+        if (event.mClass != null) {
+            proto.write(PendingEventProto.CLASS_NAME, event.mClass);
+        }
+        proto.write(PendingEventProto.TIME_MS, event.mTimeStamp);
+        proto.write(PendingEventProto.FLAGS, event.mFlags);
+        proto.write(PendingEventProto.TYPE, event.mEventType);
+        proto.write(PendingEventProto.INSTANCE_ID, event.mInstanceId);
+        if (event.mTaskRootPackage != null) {
+            proto.write(PendingEventProto.TASK_ROOT_PACKAGE, event.mTaskRootPackage);
+        }
+        if (event.mTaskRootClass != null) {
+            proto.write(PendingEventProto.TASK_ROOT_CLASS, event.mTaskRootClass);
+        }
+        switch (event.mEventType) {
+            case UsageEvents.Event.CONFIGURATION_CHANGE:
+                if (event.mConfiguration != null) {
+                    event.mConfiguration.writeToProto(proto, PendingEventProto.CONFIG);
+                }
+                break;
+            case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
+                if (event.mBucketAndReason != 0) {
+                    proto.write(PendingEventProto.STANDBY_BUCKET, event.mBucketAndReason);
+                }
+                break;
+            case UsageEvents.Event.SHORTCUT_INVOCATION:
+                if (event.mShortcutId != null) {
+                    proto.write(PendingEventProto.SHORTCUT_ID, event.mShortcutId);
+                }
+                break;
+            case UsageEvents.Event.NOTIFICATION_INTERRUPTION:
+                if (event.mNotificationChannelId != null) {
+                    proto.write(PendingEventProto.NOTIFICATION_CHANNEL_ID,
+                            event.mNotificationChannelId);
+                }
+                break;
+        }
+    }
+
+    /**
+     * Writes the pending events to a ProtoBuf file.
+     *
+     * @param out the output stream to which to write the pending events.
+     * @param events the list of pending events.
+     */
+    static void writePendingEvents(OutputStream out, LinkedList<UsageEvents.Event> events)
+            throws IOException, IllegalArgumentException {
+        final ProtoOutputStream proto = new ProtoOutputStream(out);
+        final int eventCount = events.size();
+        for (int i = 0; i < eventCount; i++) {
+            try {
+                final long token = proto.start(IntervalStatsObfuscatedProto.PENDING_EVENTS);
+                writePendingEvent(proto, events.get(i));
+                proto.end(token);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Unable to write some pending events to proto.", e);
+            }
+        }
+        proto.flush();
+    }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 2cd207f..5d03e151 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -141,6 +141,7 @@
     static final int MSG_UID_STATE_CHANGED = 3;
     static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4;
     static final int MSG_UNLOCKED_USER = 5;
+    static final int MSG_PACKAGE_REMOVED = 6;
 
     private final Object mLock = new Object();
     Handler mHandler;
@@ -148,7 +149,6 @@
     UserManager mUserManager;
     PackageManager mPackageManager;
     PackageManagerInternal mPackageManagerInternal;
-    PackageMonitor mPackageMonitor;
     IDeviceIdleController mDeviceIdleController;
     // Do not use directly. Call getDpmInternal() instead
     DevicePolicyManagerInternal mDpmInternal;
@@ -164,6 +164,8 @@
     /** Manages app time limit observers */
     AppTimeLimitController mAppTimeLimit;
 
+    private final PackageMonitor mPackageMonitor = new MyPackageMonitor();
+
     // A map maintaining a queue of events to be reported per user.
     private final SparseArray<LinkedList<Event>> mReportedEvents = new SparseArray<>();
     final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
@@ -243,6 +245,8 @@
 
         mAppStandby.addListener(mStandbyChangeListener);
 
+        mPackageMonitor.register(getContext(), null, UserHandle.ALL, true);
+
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
         getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
@@ -617,7 +621,7 @@
             final AtomicFile af = new AtomicFile(pendingEventsFiles[i]);
             try {
                 try (FileInputStream in = af.openRead()) {
-                    UsageStatsProto.readPendingEvents(in, pendingEvents);
+                    UsageStatsProtoV2.readPendingEvents(in, pendingEvents);
                 }
             } catch (IOException e) {
                 // Even if one file read fails, exit here to keep all events in order on disk -
@@ -647,11 +651,11 @@
         FileOutputStream fos = null;
         try {
             fos = af.startWrite();
-            UsageStatsProto.writePendingEvents(fos, pendingEvents);
+            UsageStatsProtoV2.writePendingEvents(fos, pendingEvents);
             af.finishWrite(fos);
             fos = null;
             pendingEvents.clear();
-        } catch (IOException e) {
+        } catch (IOException | IllegalArgumentException e) {
             Slog.e(TAG, "Failed to write " + pendingEventsFile.getAbsolutePath()
                     + " for user " + userId);
         } finally {
@@ -843,6 +847,26 @@
     }
 
     /**
+     * Called by the Handler for message MSG_PACKAGE_REMOVED.
+     */
+    private void onPackageRemoved(int userId, String packageName) {
+        synchronized (mLock) {
+            final long timeRemoved = System.currentTimeMillis();
+            if (!mUserUnlockedStates.get(userId, false)) {
+                // If user is not unlocked and a package is removed for them, we will handle it
+                // when the user service is initialized and package manager is queried.
+                return;
+            }
+            final UserUsageStatsService userService = mUserState.get(userId);
+            if (userService == null) {
+                return;
+            }
+
+            userService.onPackageRemoved(packageName, timeRemoved);
+        }
+    }
+
+    /**
      * Called by the Binder stub.
      */
     List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
@@ -1030,21 +1054,13 @@
                                 ipw.decreaseIndent();
                             }
                         } else {
-                            final int user;
-                            try {
-                                user = Integer.valueOf(args[i + 1]);
-                            } catch (NumberFormatException nfe) {
-                                ipw.println("invalid user specified.");
-                                return;
+                            final int user = parseUserIdFromArgs(args, i, ipw);
+                            if (user != UserHandle.USER_NULL) {
+                                final String[] remainingArgs = Arrays.copyOfRange(
+                                        args, i + 2, args.length);
+                                // dump everything for the specified user
+                                mUserState.get(user).dumpFile(ipw, remainingArgs);
                             }
-                            if (mUserState.indexOfKey(user) < 0) {
-                                ipw.println("the specified user does not exist.");
-                                return;
-                            }
-                            final String[] remainingArgs = Arrays.copyOfRange(
-                                    args, i + 2, args.length);
-                            // dump everything for the specified user
-                            mUserState.get(user).dumpFile(ipw, remainingArgs);
                         }
                         return;
                     } else if ("database-info".equals(arg)) {
@@ -1059,19 +1075,11 @@
                                 ipw.decreaseIndent();
                             }
                         } else {
-                            final int user;
-                            try {
-                                user = Integer.valueOf(args[i + 1]);
-                            } catch (NumberFormatException nfe) {
-                                ipw.println("invalid user specified.");
-                                return;
+                            final int user = parseUserIdFromArgs(args, i, ipw);
+                            if (user != UserHandle.USER_NULL) {
+                                // dump info only for the specified user
+                                mUserState.get(user).dumpDatabaseInfo(ipw);
                             }
-                            if (mUserState.indexOfKey(user) < 0) {
-                                ipw.println("the specified user does not exist.");
-                                return;
-                            }
-                            // dump info only for the specified user
-                            mUserState.get(user).dumpDatabaseInfo(ipw);
                         }
                         return;
                     } else if ("appstandby".equals(arg)) {
@@ -1079,15 +1087,18 @@
                         return;
                     } else if ("stats-directory".equals(arg)) {
                         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
-                        final int userId;
-                        try {
-                            userId = Integer.valueOf(args[i + 1]);
-                        } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
-                            ipw.println("invalid user specified.");
-                            return;
+                        final int userId = parseUserIdFromArgs(args, i, ipw);
+                        if (userId != UserHandle.USER_NULL) {
+                            ipw.println(new File(Environment.getDataSystemCeDirectory(userId),
+                                    "usagestats").getAbsolutePath());
                         }
-                        ipw.println(new File(Environment.getDataSystemCeDirectory(userId),
-                                "usagestats").getAbsolutePath());
+                        return;
+                    } else if ("mappings".equals(arg)) {
+                        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                        final int userId = parseUserIdFromArgs(args, i, ipw);
+                        if (userId != UserHandle.USER_NULL) {
+                            mUserState.get(userId).dumpMappings(ipw);
+                        }
                         return;
                     } else if (arg != null && !arg.startsWith("-")) {
                         // Anything else that doesn't start with '-' is a pkg to filter
@@ -1126,6 +1137,21 @@
         }
     }
 
+    private int parseUserIdFromArgs(String[] args, int index, IndentingPrintWriter ipw) {
+        final int userId;
+        try {
+            userId = Integer.valueOf(args[index + 1]);
+        } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
+            ipw.println("invalid user specified.");
+            return UserHandle.USER_NULL;
+        }
+        if (mUserState.indexOfKey(userId) < 0) {
+            ipw.println("the specified user does not exist.");
+            return UserHandle.USER_NULL;
+        }
+        return userId;
+    }
+
     class H extends Handler {
         public H(Looper looper) {
             super(looper);
@@ -1157,7 +1183,9 @@
                 case MSG_REMOVE_USER:
                     onUserRemoved(msg.arg1);
                     break;
-
+                case MSG_PACKAGE_REMOVED:
+                    onPackageRemoved(msg.arg1, (String) msg.obj);
+                    break;
                 case MSG_UID_STATE_CHANGED: {
                     final int uid = msg.arg1;
                     final int procState = msg.arg2;
@@ -2105,4 +2133,13 @@
             return mAppTimeLimit.getAppUsageLimit(packageName, user);
         }
     }
+
+    private class MyPackageMonitor extends PackageMonitor {
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            mHandler.obtainMessage(MSG_PACKAGE_REMOVED, getChangingUserId(), 0, packageName)
+                    .sendToTarget();
+            super.onPackageRemoved(packageName, uid);
+        }
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 1560b9e..23df1c5 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -34,7 +34,10 @@
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Configuration;
+import android.os.Process;
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
@@ -44,6 +47,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
 import com.android.server.usage.UsageStatsDatabase.StatCombiner;
 
 import java.io.File;
@@ -51,6 +55,7 @@
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 
 /**
@@ -108,6 +113,7 @@
     }
 
     void init(final long currentTimeMillis) {
+        readPackageMappingsLocked();
         mDatabase.init(currentTimeMillis);
 
         int nullCount = 0;
@@ -169,6 +175,54 @@
         persistActiveStats();
     }
 
+    void onPackageRemoved(String packageName, long timeRemoved) {
+        mDatabase.onPackageRemoved(packageName, timeRemoved);
+    }
+
+    private void readPackageMappingsLocked() {
+        mDatabase.readMappingsLocked();
+        cleanUpPackageMappingsLocked();
+    }
+
+    /**
+     * Queries Package Manager for a list of installed packages and removes those packages from
+     * mPackagesTokenData which are not installed any more.
+     * This will only happen once per device boot, when the user is unlocked for the first time.
+     */
+    private void cleanUpPackageMappingsLocked() {
+        final long timeNow = System.currentTimeMillis();
+        /*
+         Note (b/142501248): PackageManagerInternal#getInstalledApplications is not lightweight.
+         Once its implementation is updated, or it's replaced with a better alternative, update
+         the call here to use it. For now, using the heavy #getInstalledApplications is okay since
+         this clean-up is only performed once every boot.
+         */
+        final PackageManagerInternal packageManagerInternal =
+                LocalServices.getService(PackageManagerInternal.class);
+        if (packageManagerInternal == null) {
+            return;
+        }
+        final List<ApplicationInfo> installedPackages =
+                packageManagerInternal.getInstalledApplications(0, mUserId, Process.SYSTEM_UID);
+        // convert the package list to a set for easy look-ups
+        final HashSet<String> packagesSet = new HashSet<>(installedPackages.size());
+        for (int i = installedPackages.size() - 1; i >= 0; i--) {
+            packagesSet.add(installedPackages.get(i).packageName);
+        }
+        final List<String> removedPackages = new ArrayList<>();
+        // populate list of packages that are found in the mappings but not in the installed list
+        for (int i = mDatabase.mPackagesTokenData.packagesToTokensMap.size() - 1; i >= 0; i--) {
+            if (!packagesSet.contains(mDatabase.mPackagesTokenData.packagesToTokensMap.keyAt(i))) {
+                removedPackages.add(mDatabase.mPackagesTokenData.packagesToTokensMap.keyAt(i));
+            }
+        }
+
+        // remove packages in the mappings that are no longer installed
+        for (int i = removedPackages.size() - 1; i >= 0; i--) {
+            mDatabase.mPackagesTokenData.removePackage(removedPackages.get(i), timeNow);
+        }
+    }
+
     private void onTimeChanged(long oldTime, long newTime) {
         persistActiveStats();
         mDatabase.onTimeChanged(newTime - oldTime);
@@ -400,6 +454,7 @@
             if (results == null) {
                 results = new ArrayList<>();
             }
+            mDatabase.filterStats(currentStats);
             combiner.combine(currentStats, true, results);
         }
 
@@ -524,6 +579,8 @@
         if (mStatsChanged) {
             Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
             try {
+                mDatabase.obfuscateCurrentStats(mCurrentStats);
+                mDatabase.writeMappingsLocked();
                 for (int i = 0; i < mCurrentStats.length; i++) {
                     mDatabase.putUsageStats(i, mCurrentStats[i]);
                 }
@@ -700,6 +757,10 @@
         mDatabase.dump(ipw, false);
     }
 
+    void dumpMappings(IndentingPrintWriter ipw) {
+        mDatabase.dumpMappings(ipw);
+    }
+
     void dumpFile(IndentingPrintWriter ipw, String[] args) {
         if (args == null || args.length == 0) {
             // dump all files for every interval for specified user
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
index 0535d71..ff7f393 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
@@ -52,6 +52,7 @@
                                                 int length, byte type) {
         UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
         int subClass = interfaceDesc.getUsbSubclass();
+        // TODO shouldn't this switch on subtype?
         switch (subClass) {
             case AUDIO_AUDIOCONTROL:
                 if (UsbDescriptorParser.DEBUG) {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 8e7babb..b230e4b 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -26,7 +26,7 @@
  */
 public final class UsbDescriptorParser {
     private static final String TAG = "UsbDescriptorParser";
-    public static final boolean DEBUG = true;
+    public static final boolean DEBUG = false;
 
     private final String mDeviceAddr;
 
@@ -43,6 +43,11 @@
     // Obtained from the first AudioClass Header descriptor.
     private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
 
+    // The VideoClass spec implemented by the VideoClass Interfaces
+    // This may well be different than the overall USB Spec.
+    // Obtained from the first VidieoClass Header descriptor.
+    private int mVCInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
+
     /**
      * Connect this parser to an existing set of already parsed descriptors.
      * This is useful for reporting.
@@ -90,6 +95,14 @@
         return mACInterfacesSpec;
     }
 
+    public void setVCInterfaceSpec(int spec) {
+        mVCInterfacesSpec = spec;
+    }
+
+    public int getVCInterfaceSpec() {
+        return mVCInterfacesSpec;
+    }
+
     private class UsbDescriptorsStreamFormatException extends Exception {
         String mMessage;
         UsbDescriptorsStreamFormatException(String message) {
@@ -186,19 +199,20 @@
                             break;
 
                         case UsbDescriptor.CLASSID_VIDEO:
-                            Log.d(TAG, "  UsbDescriptor.CLASSID_VIDEO subType:0x"
-                                    + Integer.toHexString(stream.getByte()));
+                            if (DEBUG) {
+                                Log.d(TAG, "  UsbDescriptor.CLASSID_VIDEO");
+                            }
                             descriptor = UsbVCInterface.allocDescriptor(this, stream, length, type);
                             break;
 
                         case UsbDescriptor.CLASSID_AUDIOVIDEO:
-                            Log.d(TAG, "  UsbDescriptor.CLASSID_AUDIOVIDEO subType:0x"
-                                    + Integer.toHexString(stream.getByte()));
+                            if (DEBUG) {
+                                Log.d(TAG, "  UsbDescriptor.CLASSID_AUDIOVIDEO");
+                            }
                             break;
 
                         default:
-                            Log.d(TAG, "  Unparsed Class-specific Interface:0x"
-                                    + Integer.toHexString(mCurInterfaceDescriptor.getUsbClass()));
+                            Log.w(TAG, "  Unparsed Class-specific");
                             break;
                     }
                 }
@@ -206,23 +220,32 @@
 
             case UsbDescriptor.DESCRIPTORTYPE_CLASSSPECIFIC_ENDPOINT:
                 if (mCurInterfaceDescriptor != null) {
-                    switch (mCurInterfaceDescriptor.getUsbClass()) {
+                    int subClass = mCurInterfaceDescriptor.getUsbClass();
+                    switch (subClass) {
                         case UsbDescriptor.CLASSID_AUDIO:
                             descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
                             break;
-                        case UsbDescriptor.CLASSID_VIDEO:
-                            Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO subType:0x"
-                                    + Integer.toHexString(stream.getByte()));
-                            descriptor = UsbVCEndpoint.allocDescriptor(this, length, type);
+
+                        case UsbDescriptor.CLASSID_VIDEO: {
+                            Byte subtype = stream.getByte();
+                            if (DEBUG) {
+                                Log.d(TAG, "UsbDescriptor.CLASSID_VIDEO type:0x"
+                                        + Integer.toHexString(type));
+                            }
+                            descriptor = UsbVCEndpoint.allocDescriptor(this, length, type, subtype);
+                        }
                             break;
 
                         case UsbDescriptor.CLASSID_AUDIOVIDEO:
-                            Log.d(TAG, "UsbDescriptor.CLASSID_AUDIOVIDEO subType:0x"
-                                    + Integer.toHexString(stream.getByte()));
+                            if (DEBUG) {
+                                Log.d(TAG, "UsbDescriptor.CLASSID_AUDIOVIDEO type:0x"
+                                        + Integer.toHexString(type));
+                            }
                             break;
+
                         default:
-                            Log.d(TAG, "  Unparsed Class-specific Endpoint:0x"
-                                    + Integer.toHexString(mCurInterfaceDescriptor.getUsbClass()));
+                            Log.w(TAG, "  Unparsed Class-specific Endpoint:0x"
+                                    + Integer.toHexString(subClass));
                             break;
                     }
                 }
@@ -555,6 +578,30 @@
     /**
      * @hide
      */
+    public boolean hasVideoCapture() {
+        for (UsbDescriptor descriptor : mDescriptors) {
+            if (descriptor instanceof UsbVCInputTerminal) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasVideoPlayback() {
+        for (UsbDescriptor descriptor : mDescriptors) {
+            if (descriptor instanceof UsbVCOutputTerminal) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
     public boolean hasHIDInterface() {
         ArrayList<UsbDescriptor> descriptors =
                 getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID);
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
index 9739243..b1cbbaf 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
@@ -160,7 +160,8 @@
         return new UsbDevice.Builder(parser.getDeviceAddr(), mVendorID,
                 mProductID, mDevClass, mDevSubClass, mProtocol, mfgName, prodName, versionString,
                 configs, serialStr, parser.hasAudioPlayback(), parser.hasAudioCapture(),
-                parser.hasMIDIInterface());
+                parser.hasMIDIInterface(),
+                parser.hasVideoPlayback(), parser.hasVideoCapture());
     }
 
     @Override
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java
index 39fbc0d..f9acece 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCEndpoint.java
@@ -20,41 +20,54 @@
 /**
  * @hide
  * A video class-specific Endpoint
- * see
+ * see USB_Video_Class_1.1.pdf - 3.10 VideoStreaming Endpoint Descriptors
  */
 abstract class UsbVCEndpoint extends UsbDescriptor {
     private static final String TAG = "UsbVCEndpoint";
 
-    UsbVCEndpoint(int length, byte type, int subclass) {
+
+    public static final byte VCEP_UNDEFINED = 0x00;
+    public static final byte VCEP_GENERAL   = 0x01;
+    public static final byte VCEP_ENDPOINT  = 0x02;
+    public static final byte VCEP_INTERRUPT = 0x03;
+
+    UsbVCEndpoint(int length, byte type) {
         super(length, type);
-        // mSubclass = subclass;
     }
 
     public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
-                                                int length, byte type) {
+                                                int length, byte type, byte subtype) {
         UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
-        int subClass = interfaceDesc.getUsbSubclass();
-        switch (subClass) {
-//            case AUDIO_AUDIOCONTROL:
-//                if (UsbDescriptorParser.DEBUG) {
-//                    Log.i(TAG, "---> AUDIO_AUDIOCONTROL");
-//                }
-//                return new UsbACAudioControlEndpoint(length, type, subClass);
-//
-//            case AUDIO_AUDIOSTREAMING:
-//                if (UsbDescriptorParser.DEBUG) {
-//                    Log.i(TAG, "---> AUDIO_AUDIOSTREAMING");
-//                }
-//                return new UsbACAudioStreamEndpoint(length, type, subClass);
-//
-//            case AUDIO_MIDISTREAMING:
-//                if (UsbDescriptorParser.DEBUG) {
-//                    Log.i(TAG, "---> AUDIO_MIDISTREAMING");
-//                }
-//                return new UsbACMidiEndpoint(length, type, subClass);
+
+        // TODO - create classes for each specific subtype
+        //  (don't need it to answer if this device supports video
+        switch (subtype) {
+            case VCEP_UNDEFINED:
+                if (UsbDescriptorParser.DEBUG) {
+                    Log.d(TAG, "---> VCEP_UNDEFINED");
+                }
+                return null;
+
+            case VCEP_GENERAL:
+                if (UsbDescriptorParser.DEBUG) {
+                    Log.d(TAG, "---> VCEP_GENERAL");
+                }
+                return null;
+
+            case VCEP_ENDPOINT:
+                if (UsbDescriptorParser.DEBUG) {
+                    Log.d(TAG, "---> VCEP_ENDPOINT");
+                }
+                return null;
+
+            case VCEP_INTERRUPT:
+                if (UsbDescriptorParser.DEBUG) {
+                    Log.d(TAG, "---> VCEP_INTERRUPT");
+                }
+                return null;
 
             default:
-                Log.w(TAG, "Unknown Video Class Endpoint id:0x" + Integer.toHexString(subClass));
+                Log.w(TAG, "Unknown Video Class Endpoint id:0x" + Integer.toHexString(subtype));
                 return null;
         }
     }
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCHeader.java
new file mode 100644
index 0000000..3fc4224
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCHeader.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import android.util.Log;
+
+import com.android.server.usb.descriptors.report.ReportCanvas;
+
+/**
+ * @hide
+ * A video class-specific Interface Header.
+ * see USB_Video_Class_1.1.pdf section 3.9.2 - Class-Specific VS Interface Descriptors
+ */
+public final class UsbVCHeader extends UsbVCHeaderInterface {
+    private static final String TAG = "UsbVCHeader";
+
+    // TODO Add data members for this descriptor's data
+
+    public UsbVCHeader(int length, byte type, byte subtype, int spec) {
+        super(length, type, subtype, spec);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        if (UsbDescriptorParser.DEBUG) {
+            Log.d(TAG, " ---> parseRawDescriptors()");
+        }
+        // TODO parse data members for this descriptor's data
+        return super.parseRawDescriptors(stream);
+    }
+
+    @Override
+    public void report(ReportCanvas canvas) {
+        super.report(canvas);
+        // TODO add reporting specific to this descriptor
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCHeaderInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCHeaderInterface.java
new file mode 100644
index 0000000..3725091
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCHeaderInterface.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import com.android.server.usb.descriptors.report.ReportCanvas;
+
+/**
+ * @hide
+ * A video class-specific Interface Header super class.
+ * see USB_Video_Class_1.1.pdf section 3.9.2 - Class-Specific VS Interface Descriptors
+ */
+public abstract class UsbVCHeaderInterface extends UsbVCInterface {
+    private static final String TAG = "UsbVCHeaderInterface";
+
+    protected int mVDCRelease;  // Video Device Class Specification Release (BCD).
+    protected int mTotalLength; // Total number of bytes returned for the class-specific
+    // VideoControl interface descriptor. Includes the combined length
+    // of this descriptor header and all Unit and Terminal descriptors.
+
+    public UsbVCHeaderInterface(
+            int length, byte type, byte subtype, int vdcRelease) {
+        super(length, type, subtype);
+        mVDCRelease = vdcRelease;
+    }
+
+    public int getVDCRelease() {
+        return mVDCRelease;
+    }
+
+    public int getTotalLength() {
+        return mTotalLength;
+    }
+
+    @Override
+    public void report(ReportCanvas canvas) {
+        super.report(canvas);
+
+        canvas.openList();
+        canvas.writeListItem("Release: " + ReportCanvas.getBCDString(getVDCRelease()));
+        canvas.writeListItem("Total Length: " + getTotalLength());
+        canvas.closeList();
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.java
new file mode 100644
index 0000000..df63795
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCInputTerminal.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.usb.descriptors;
+
+import android.util.Log;
+
+import com.android.server.usb.descriptors.report.ReportCanvas;
+
+/**
+ * @hide
+ * A video class-specific Input terminal interface.
+ * see USB_Video_Class_1.1.pdf section 3.7.2.1 Input Terminal Descriptor
+ */
+public final class UsbVCInputTerminal extends UsbVCInterface {
+    private static final String TAG = "UsbVCInputTerminal";
+
+    // TODO Define members to hold the data from this descriptor
+    public UsbVCInputTerminal(int length, byte type, byte subtype) {
+        super(length, type, subtype);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        // TODO Parse the data from this descriptor
+        if (UsbDescriptorParser.DEBUG) {
+            Log.d(TAG, " ---> parseRawDescriptors()");
+        }
+        return super.parseRawDescriptors(stream);
+    }
+
+    @Override
+    public void report(ReportCanvas canvas) {
+        // TODO Add reporting specific to this descriptor
+        super.report(canvas);
+    }
+};
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java
index c9eb1ec..46263dd 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCInterface.java
@@ -29,20 +29,17 @@
     public static final byte VCI_UNDEFINED          = 0x00;
     public static final byte VCI_VEADER             = 0x01;
     public static final byte VCI_INPUT_TERMINAL     = 0x02;
-    public static final byte VCI_VOUTPUT_TERMINAL   = 0x03;
+    public static final byte VCI_OUTPUT_TERMINAL    = 0x03;
     public static final byte VCI_SELECTOR_UNIT      = 0x04;
-    public static final byte VCI_VROCESSING_UNIT    = 0x05;
-    public static final byte VCI_VEXTENSION_UNIT    = 0x06;
+    public static final byte VCI_PROCESSING_UNIT    = 0x05;
+    public static final byte VCI_EXTENSION_UNIT     = 0x06;
 
-   // See “Universal Serial Bus Device Class Definition for Video
+    // See “Universal Serial Bus Device Class Definition for Video
     protected final byte mSubtype;  // 2:1 HEADER descriptor subtype
-    protected final int mSubclass;  // from the mSubclass member of the
-    // "enclosing" Interface Descriptor
 
-    public UsbVCInterface(int length, byte type, byte subtype, int subclass) {
+    public UsbVCInterface(int length, byte type, byte subtype) {
         super(length, type);
         mSubtype = subtype;
-        mSubclass = subclass;
     }
 
     /**
@@ -52,12 +49,10 @@
                                                 int length, byte type) {
         byte subtype = stream.getByte();
         UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
-        int subClass = interfaceDesc.getUsbSubclass();
         if (UsbDescriptorParser.DEBUG) {
-            Log.d(TAG, "  Video Class-specific Interface subClass:0x"
-                    + Integer.toHexString(subClass));
+            Log.d(TAG, "  Video Class-specific Interface subtype: " + subtype);
         }
-        switch (subClass) {
+        switch (subtype) {
             // TODO - Create descriptor classes and parse these...
             case VCI_UNDEFINED:
                 if (UsbDescriptorParser.DEBUG) {
@@ -66,44 +61,51 @@
                 break;
 
             case VCI_VEADER:
+            {
                 if (UsbDescriptorParser.DEBUG) {
                     Log.d(TAG, " ---> VCI_VEADER");
                 }
-                break;
+                int vcInterfaceSpec = stream.unpackUsbShort();
+                parser.setVCInterfaceSpec(vcInterfaceSpec);
+                if (UsbDescriptorParser.DEBUG) {
+                    Log.d(TAG, "  vcInterfaceSpec:0x" + Integer.toHexString(vcInterfaceSpec));
+                }
+                return new UsbVCHeader(length, type, subtype, vcInterfaceSpec);
+            }
 
             case VCI_INPUT_TERMINAL:
                 if (UsbDescriptorParser.DEBUG) {
                     Log.d(TAG, " ---> VCI_INPUT_TERMINAL");
                 }
-                break;
+                return new UsbVCInputTerminal(length, type, subtype);
 
-            case VCI_VOUTPUT_TERMINAL:
+            case VCI_OUTPUT_TERMINAL:
                 if (UsbDescriptorParser.DEBUG) {
-                    Log.d(TAG, " ---> VCI_VOUTPUT_TERMINAL");
+                    Log.d(TAG, " ---> VCI_OUTPUT_TERMINAL");
                 }
-                break;
+                return new UsbVCOutputTerminal(length, type, subtype);
 
             case VCI_SELECTOR_UNIT:
                 if (UsbDescriptorParser.DEBUG) {
                     Log.d(TAG, " ---> VCI_SELECTOR_UNIT");
                 }
-                break;
+                return new UsbVCSelectorUnit(length, type, subtype);
 
-            case VCI_VROCESSING_UNIT:
+            case VCI_PROCESSING_UNIT:
                 if (UsbDescriptorParser.DEBUG) {
-                    Log.d(TAG, " ---> VCI_VROCESSING_UNIT");
+                    Log.d(TAG, " ---> VCI_PROCESSING_UNIT");
                 }
-                break;
+                return new UsbVCProcessingUnit(length, type, subtype);
 
-            case VCI_VEXTENSION_UNIT:
+            case VCI_EXTENSION_UNIT:
                 if (UsbDescriptorParser.DEBUG) {
-                    Log.d(TAG, " ---> VCI_VEXTENSION_UNIT");
+                    Log.d(TAG, " ---> VCI_EXTENSION_UNIT");
                 }
                 break;
 
             default:
-                Log.w(TAG, "Unknown Video Class Interface Subclass: 0x"
-                        + Integer.toHexString(subClass));
+                Log.w(TAG, "Unknown Video Class Interface subtype: 0x"
+                        + Integer.toHexString(subtype));
                 return null;
         }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.java
new file mode 100644
index 0000000..4aa8ca2
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCOutputTerminal.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.usb.descriptors;
+
+import android.util.Log;
+
+import com.android.server.usb.descriptors.report.ReportCanvas;
+
+/**
+ * @hide
+ * A video class-specific Output Terminal Descriptor.
+ * see USB_Video_Class_1.1.pdf section 3.7.2.2 Output Terminal Descriptor
+ */
+public final class UsbVCOutputTerminal extends UsbVCInterface {
+    private static final String TAG = "UsbVCOutputTerminal";
+
+    // TODO Add members for the data in this descriptor
+    public UsbVCOutputTerminal(int length, byte type, byte subtype) {
+        super(length, type, subtype);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        // TODO Parse the data in this descriptor
+        if (UsbDescriptorParser.DEBUG) {
+            Log.d(TAG, " ---> parseRawDescriptors()");
+        }
+        return super.parseRawDescriptors(stream);
+    }
+
+    @Override
+    public void report(ReportCanvas canvas) {
+        super.report(canvas);
+        // TODO Add reporting specific to this descriptor
+    }
+};
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
new file mode 100644
index 0000000..5ce842e
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCProcessingUnit.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import android.util.Log;
+
+import com.android.server.usb.descriptors.report.ReportCanvas;
+
+/**
+ * @hide
+ * An video class-specific Processing Unit Interface.
+ * see USB_Video_Class_1.1.pdf section Table 3-8 Processing Unit Descriptor
+ */
+public final class UsbVCProcessingUnit extends UsbVCInterface {
+    private static final String TAG = "UsbVCProcessingUnit";
+
+    // TODO Add data members for this descriptor
+
+    public UsbVCProcessingUnit(int length, byte type, byte subtype) {
+        super(length, type, subtype);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        // TODO Parse this descriptor
+        if (UsbDescriptorParser.DEBUG) {
+            Log.d(TAG, " ---> parseRawDescriptors()");
+        }
+        return super.parseRawDescriptors(stream);
+    }
+
+    @Override
+    public void report(ReportCanvas canvas) {
+        super.report(canvas);
+        // TODO Add reporting specific to this descriptor
+    }
+};
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
new file mode 100644
index 0000000..8e9b0d8
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbVCSelectorUnit.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usb.descriptors;
+
+import android.util.Log;
+
+import com.android.server.usb.descriptors.report.ReportCanvas;
+
+/**
+ * @hide
+ * An video class-specific Selector Unit Descriptor
+ * see USB_Video_Class_1.1.pdf section 3.7.2.4
+ */
+public final class UsbVCSelectorUnit extends UsbVCInterface {
+    private static final String TAG = "UsbVCSelectorUnit";
+
+    // TODO Add data members for this descriptor
+
+    public UsbVCSelectorUnit(int length, byte type, byte subtype) {
+        super(length, type, subtype);
+    }
+
+    @Override
+    public int parseRawDescriptors(ByteStream stream) {
+        // TODO Parse this descriptor
+        if (UsbDescriptorParser.DEBUG) {
+            Log.d(TAG, " ---> parseRawDescriptors()");
+        }
+        return super.parseRawDescriptors(stream);
+    }
+
+    @Override
+    public void report(ReportCanvas canvas) {
+        super.report(canvas);
+        // TODO Add reporting specific to this descriptor
+    }
+};
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 3f348a4..60290e3 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -2049,6 +2049,10 @@
                 return "DISCONNECTING";
             case STATE_SELECT_PHONE_ACCOUNT:
                 return "SELECT_PHONE_ACCOUNT";
+            case STATE_SIMULATED_RINGING:
+                return "SIMULATED_RINGING";
+            case STATE_AUDIO_PROCESSING:
+                return "AUDIO_PROCESSING";
             default:
                 Log.w(Call.class, "Unknown state %d", state);
                 return "UNKNOWN";
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 3e02871..56b7236 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -24,8 +24,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.AccessNetworkConstants.TransportType;
-
 import android.telephony.Annotation.NetworkType;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -90,7 +90,7 @@
      * Dual Connectivity(EN-DC).
      * @hide
      */
-    public static final int NR_STATE_NONE = -1;
+    public static final int NR_STATE_NONE = 0;
 
     /**
      * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) but
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 1d00b4f..f503348 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -102,7 +102,7 @@
      * Indicates frequency range is unknown.
      * @hide
      */
-    public static final int FREQUENCY_RANGE_UNKNOWN = -1;
+    public static final int FREQUENCY_RANGE_UNKNOWN = 0;
 
     /**
      * Indicates the frequency range is below 1GHz.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 06a7370..f83520f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10237,11 +10237,13 @@
 
     /**
      * Action set from carrier signalling broadcast receivers to enable/disable radio
-     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
      * @param subId the subscription ID that this action applies to.
      * @param enabled control enable or disable radio.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void carrierActionSetRadioEnabled(int subId, boolean enabled) {
         try {
             ITelephony service = getITelephony();
@@ -10256,11 +10258,13 @@
     /**
      * Action set from carrier signalling broadcast receivers to start/stop reporting default
      * network available events
-     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
      * @param subId the subscription ID that this action applies to.
      * @param report control start/stop reporting network status.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void carrierActionReportDefaultNetworkStatus(int subId, boolean report) {
         try {
             ITelephony service = getITelephony();
@@ -10274,10 +10278,12 @@
 
     /**
      * Action set from carrier signalling broadcast receivers to reset all carrier actions
-     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     * Permissions {@link android.Manifest.permission.MODIFY_PHONE_STATE} is required.
      * @param subId the subscription ID that this action applies to.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public void carrierActionResetAll(int subId) {
         try {
             ITelephony service = getITelephony();
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 39e00cc..f79a5c6 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1778,21 +1778,6 @@
      boolean isInEmergencySmsMode();
 
     /**
-     * Get a list of SMS apps on a user.
-     */
-    String[] getSmsApps(int userId);
-
-    /**
-     * Get the default SMS app on a given user.
-     */
-    String getDefaultSmsApp(int userId);
-
-    /**
-     * Set the default SMS app to a given package on a given user.
-     */
-    void setDefaultSmsApp(int userId, String packageName);
-
-    /**
      * Return the modem radio power state for slot index.
      *
      */
diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml
index c132007..b4f2663 100644
--- a/tests/BootImageProfileTest/AndroidTest.xml
+++ b/tests/BootImageProfileTest/AndroidTest.xml
@@ -22,8 +22,8 @@
         <!-- we need this magic flag, otherwise it always reboots and breaks the selinux -->
         <option name="force-skip-system-props" value="true" />
 
-        <option name="run-command" value="setprop dalvik.vm.profilesystemserver true" />
-        <option name="run-command" value="setprop dalvik.vm.profilebootclasspath true" />
+        <option name="run-command" value="device_config put runtime_native_boot profilesystemserver true" />
+        <option name="run-command" value="device_config put runtime_native_boot profilebootclasspath true" />
 
         <!-- Profiling does not pick up the above changes we restart the shell -->
         <option name="run-command" value="stop" />
diff --git a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
index fe1d9d2..ccdd452 100644
--- a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
+++ b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
@@ -46,9 +46,11 @@
      */
     @Test
     public void testProperties() throws Exception {
-        String res = mTestDevice.getProperty("dalvik.vm.profilebootclasspath");
+        String res = mTestDevice.getProperty(
+                "persist.device_config.runtime_native_boot.profilebootclasspath");
         assertTrue("profile boot class path not enabled", res != null && res.equals("true"));
-        res = mTestDevice.getProperty("dalvik.vm.profilesystemserver");
+        res = mTestDevice.getProperty(
+                "persist.device_config.runtime_native_boot.profilesystemserver");
         assertTrue("profile system server not enabled", res != null && res.equals("true"));
     }
 
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 344cc9e..79b8b46 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
+import android.provider.DeviceConfig;
 import android.text.TextUtils;
 
 import androidx.test.InstrumentationRegistry;
@@ -51,6 +52,8 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Tests for rollback of staged installs.
  * <p>
@@ -64,6 +67,8 @@
 
     private static final String NETWORK_STACK_CONNECTOR_CLASS =
             "android.net.INetworkStackConnector";
+    private static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
+            "watchdog_trigger_failure_count";
 
     private static final String MODULE_META_DATA_PACKAGE = getModuleMetadataPackageName();
 
@@ -76,7 +81,8 @@
                 Manifest.permission.INSTALL_PACKAGES,
                 Manifest.permission.DELETE_PACKAGES,
                 Manifest.permission.TEST_MANAGE_ROLLBACKS,
-                Manifest.permission.FORCE_STOP_PACKAGES);
+                Manifest.permission.FORCE_STOP_PACKAGES,
+                Manifest.permission.WRITE_DEVICE_CONFIG);
     }
 
     /**
@@ -119,6 +125,13 @@
         assertThat(rollback).packagesContainsExactly(
                 Rollback.from(TestApp.A2).to(TestApp.A1));
         assertThat(rollback.isStaged()).isTrue();
+
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(5), false);
+        RollbackUtils.sendCrashBroadcast(TestApp.A, 4);
+        // Sleep for a while to make sure we don't trigger rollback
+        Thread.sleep(TimeUnit.SECONDS.toMillis(30));
     }
 
     /**
@@ -128,8 +141,8 @@
      */
     @Test
     public void testBadApkOnly_Phase3() throws Exception {
-        // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
-        RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+        // One more crash to trigger rollback
+        RollbackUtils.sendCrashBroadcast(TestApp.A, 1);
 
         // We expect the device to be rebooted automatically. Wait for that to happen.
         Thread.sleep(30 * 1000);
@@ -158,18 +171,6 @@
         assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1);
     }
 
-    @Test
-    public void resetNetworkStack() throws Exception {
-        RollbackManager rm = RollbackUtils.getRollbackManager();
-        String networkStack = getNetworkStackPackageName();
-
-        rm.expireRollbackForPackage(networkStack);
-        Uninstall.packages(networkStack);
-
-        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
-                        networkStack)).isNull();
-    }
-
     /**
      * Stage install ModuleMetadata package to simulate a Mainline module update.
      */
@@ -210,45 +211,37 @@
 
     @Test
     public void testNetworkFailedRollback_Phase1() throws Exception {
-        resetNetworkStack();
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        String networkStack = getNetworkStackPackageName();
+
+        rm.expireRollbackForPackage(networkStack);
+        Uninstall.packages(networkStack);
+
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                        networkStack)).isNull();
     }
 
     @Test
     public void testNetworkFailedRollback_Phase2() throws Exception {
-        assertNetworkStackRollbackAvailable();
-    }
-
-    @Test
-    public void testNetworkFailedRollback_Phase3() throws Exception {
-        assertNoNetworkStackRollbackCommitted();
-    }
-
-    @Test
-    public void testNetworkFailedRollback_Phase4() throws Exception {
-        assertNetworkStackRollbackCommitted();
-    }
-
-    @Test
-    public void assertNetworkStackRollbackAvailable() throws Exception {
         RollbackManager rm = RollbackUtils.getRollbackManager();
         assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
                         getNetworkStackPackageName())).isNotNull();
     }
 
     @Test
-    public void assertNetworkStackRollbackCommitted() throws Exception {
-        RollbackManager rm = RollbackUtils.getRollbackManager();
-        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
-                        getNetworkStackPackageName())).isNotNull();
-    }
-
-    @Test
-    public void assertNoNetworkStackRollbackCommitted() throws Exception {
+    public void testNetworkFailedRollback_Phase3() throws Exception {
         RollbackManager rm = RollbackUtils.getRollbackManager();
         assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
                         getNetworkStackPackageName())).isNull();
     }
 
+    @Test
+    public void testNetworkFailedRollback_Phase4() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+                        getNetworkStackPackageName())).isNotNull();
+    }
+
     private String getNetworkStackPackageName() {
         Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS);
         ComponentName comp = intent.resolveSystemService(
@@ -292,6 +285,32 @@
         Uninstall.packages(TestApp.A);
     }
 
+    @Test
+    public void testNetworkPassedDoesNotRollback_Phase1() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        String networkStack = getNetworkStackPackageName();
+
+        rm.expireRollbackForPackage(networkStack);
+        Uninstall.packages(networkStack);
+
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                        networkStack)).isNull();
+    }
+
+    @Test
+    public void testNetworkPassedDoesNotRollback_Phase2() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+                        getNetworkStackPackageName())).isNotNull();
+    }
+
+    @Test
+    public void testNetworkPassedDoesNotRollback_Phase3() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+                        getNetworkStackPackageName())).isNull();
+    }
+
     @Nullable
     private static String getModuleMetadataPackageName() {
         String packageName = InstrumentationRegistry.getContext().getResources().getString(
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 c1ff4c3..f7fe6c7 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
@@ -94,7 +94,6 @@
 
         // Reboot device to activate staged package
         getDevice().reboot();
-        getDevice().waitForDeviceAvailable();
 
         runPhase("testNativeWatchdogTriggersRollback_Phase2");
 
@@ -134,7 +133,6 @@
         Thread.sleep(5000);
         // Reboot device to activate staged package
         getDevice().reboot();
-        getDevice().waitForDeviceAvailable();
 
         // Verify rollback was enabled
         runPhase("testNetworkFailedRollback_Phase2");
@@ -166,7 +164,7 @@
     @Test
     public void testNetworkPassedDoesNotRollback() throws Exception {
         // Remove available rollbacks and uninstall NetworkStack on /data/
-        runPhase("resetNetworkStack");
+        runPhase("testNetworkPassedDoesNotRollback_Phase1");
         // Reduce health check deadline, here unlike the network failed case, we use
         // a longer deadline because joining a network can take a much longer time for
         // reasons external to the device than 'not joining'
@@ -180,10 +178,9 @@
         Thread.sleep(5000);
         // Reboot device to activate staged package
         getDevice().reboot();
-        getDevice().waitForDeviceAvailable();
 
         // Verify rollback was enabled
-        runPhase("assertNetworkStackRollbackAvailable");
+        runPhase("testNetworkPassedDoesNotRollback_Phase2");
 
         // Connect to internet so network health check passes
         getDevice().executeShellCommand("svc wifi enable");
@@ -196,7 +193,7 @@
         // Sleep for > health check deadline
         Thread.sleep(310000);
         // Verify rollback was not executed after health check deadline
-        runPhase("assertNoNetworkStackRollbackCommitted");
+        runPhase("testNetworkPassedDoesNotRollback_Phase3");
     }
 
     /**
diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
index 7d9d0d5..7e8a134 100644
--- a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
+++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java
@@ -30,6 +30,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.usage.IntervalStats;
+import com.android.server.usage.PackagesTokenData;
 import com.android.server.usage.UsageStatsDatabase;
 import com.android.server.usage.UsageStatsDatabase.StatCombiner;
 
@@ -79,6 +80,7 @@
         sContext = InstrumentationRegistry.getTargetContext();
         mTestDir = new File(sContext.getFilesDir(), "UsageStatsDatabasePerfTest");
         sUsageStatsDatabase = new UsageStatsDatabase(mTestDir);
+        sUsageStatsDatabase.readMappingsLocked();
         sUsageStatsDatabase.init(1);
     }
 
@@ -140,6 +142,37 @@
         }
     }
 
+    private void runObfuscateStatsTest(int packageCount, int eventsPerPackage) {
+        final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+        IntervalStats intervalStats = new IntervalStats();
+        populateIntervalStats(intervalStats, packageCount, eventsPerPackage);
+        long elapsedTimeNs = 0;
+        while (benchmarkState.keepRunning(elapsedTimeNs)) {
+            final long startTime = SystemClock.elapsedRealtimeNanos();
+            PackagesTokenData packagesTokenData = new PackagesTokenData();
+            intervalStats.obfuscateData(packagesTokenData);
+            final long endTime = SystemClock.elapsedRealtimeNanos();
+            elapsedTimeNs = endTime - startTime;
+            clearUsageStatsFiles();
+        }
+    }
+
+    private void runDeobfuscateStatsTest(int packageCount, int eventsPerPackage) {
+        final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+        IntervalStats intervalStats = new IntervalStats();
+        populateIntervalStats(intervalStats, packageCount, eventsPerPackage);
+        long elapsedTimeNs = 0;
+        while (benchmarkState.keepRunning(elapsedTimeNs)) {
+            PackagesTokenData packagesTokenData = new PackagesTokenData();
+            intervalStats.obfuscateData(packagesTokenData);
+            final long startTime = SystemClock.elapsedRealtimeNanos();
+            intervalStats.deobfuscateData(packagesTokenData);
+            final long endTime = SystemClock.elapsedRealtimeNanos();
+            elapsedTimeNs = endTime - startTime;
+            clearUsageStatsFiles();
+        }
+    }
+
     @Test
     public void testQueryUsageStats_FewPkgsLightUse() throws IOException {
         runQueryUsageStatsTest(FEW_PKGS, LIGHT_USE);
@@ -151,6 +184,16 @@
     }
 
     @Test
+    public void testObfuscateStats_FewPkgsLightUse() {
+        runObfuscateStatsTest(FEW_PKGS, LIGHT_USE);
+    }
+
+    @Test
+    public void testDeobfuscateStats_FewPkgsLightUse() {
+        runDeobfuscateStatsTest(FEW_PKGS, LIGHT_USE);
+    }
+
+    @Test
     public void testQueryUsageStats_FewPkgsHeavyUse() throws IOException {
         runQueryUsageStatsTest(FEW_PKGS, HEAVY_USE);
     }
@@ -161,6 +204,16 @@
     }
 
     @Test
+    public void testObfuscateStats_FewPkgsHeavyUse() {
+        runObfuscateStatsTest(FEW_PKGS, HEAVY_USE);
+    }
+
+    @Test
+    public void testDeobfuscateStats_FewPkgsHeavyUse() {
+        runDeobfuscateStatsTest(FEW_PKGS, HEAVY_USE);
+    }
+
+    @Test
     public void testQueryUsageStats_ManyPkgsLightUse() throws IOException {
         runQueryUsageStatsTest(MANY_PKGS, LIGHT_USE);
     }
@@ -171,6 +224,16 @@
     }
 
     @Test
+    public void testObfuscateStats_ManyPkgsLightUse() {
+        runObfuscateStatsTest(MANY_PKGS, LIGHT_USE);
+    }
+
+    @Test
+    public void testDeobfuscateStats_ManyPkgsLightUse() {
+        runDeobfuscateStatsTest(MANY_PKGS, LIGHT_USE);
+    }
+
+    @Test
     public void testQueryUsageStats_ManyPkgsHeavyUse() throws IOException {
         runQueryUsageStatsTest(MANY_PKGS, HEAVY_USE);
     }
@@ -179,4 +242,14 @@
     public void testPutUsageStats_ManyPkgsHeavyUse() throws IOException {
         runPutUsageStatsTest(MANY_PKGS, HEAVY_USE);
     }
+
+    @Test
+    public void testObfuscateStats_ManyPkgsHeavyUse() {
+        runObfuscateStatsTest(MANY_PKGS, HEAVY_USE);
+    }
+
+    @Test
+    public void testDeobfuscateStats_ManyPkgsHeavyUse() {
+        runDeobfuscateStatsTest(MANY_PKGS, HEAVY_USE);
+    }
 }
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 2ca0d1a..1569112 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -271,7 +271,7 @@
             .addCapability(NET_CAPABILITY_NOT_METERED);
         assertParcelingIsLossless(netCap);
         netCap.setSSID(TEST_SSID);
-        assertParcelSane(netCap, 11);
+        assertParcelSane(netCap, 12);
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index cf3fba8..61f37fd 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -33,6 +33,7 @@
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
@@ -492,6 +493,8 @@
         private INetworkMonitor mNetworkMonitor;
         private INetworkMonitorCallbacks mNmCallbacks;
         private int mNmValidationResult = VALIDATION_RESULT_BASE;
+        private int mProbesCompleted;
+        private int mProbesSucceeded;
         private String mNmValidationRedirectUrl = null;
         private boolean mNmProvNotificationRequested = false;
 
@@ -559,6 +562,7 @@
                 mNmProvNotificationRequested = false;
             }
 
+            mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded);
             mNmCallbacks.notifyNetworkTested(
                     mNmValidationResult, mNmValidationRedirectUrl);
 
@@ -581,7 +585,7 @@
          * @param validated Indicate if network should pretend to be validated.
          */
         public void connect(boolean validated) {
-            connect(validated, true);
+            connect(validated, true, false /* isStrictMode */);
         }
 
         /**
@@ -589,13 +593,13 @@
          * @param validated Indicate if network should pretend to be validated.
          * @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
          */
-        public void connect(boolean validated, boolean hasInternet) {
+        public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
             assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET));
 
             ConnectivityManager.NetworkCallback callback = null;
             final ConditionVariable validatedCv = new ConditionVariable();
             if (validated) {
-                setNetworkValid();
+                setNetworkValid(isStrictMode);
                 NetworkRequest request = new NetworkRequest.Builder()
                         .addTransportType(getNetworkCapabilities().getTransportTypes()[0])
                         .clearCapabilities()
@@ -620,15 +624,15 @@
             if (validated) {
                 // Wait for network to validate.
                 waitFor(validatedCv);
-                setNetworkInvalid();
+                setNetworkInvalid(isStrictMode);
             }
 
             if (callback != null) mCm.unregisterNetworkCallback(callback);
         }
 
-        public void connectWithCaptivePortal(String redirectUrl) {
-            setNetworkPortal(redirectUrl);
-            connect(false);
+        public void connectWithCaptivePortal(String redirectUrl, boolean isStrictMode) {
+            setNetworkPortal(redirectUrl, isStrictMode);
+            connect(false, true /* hasInternet */, isStrictMode);
         }
 
         public void connectWithPartialConnectivity() {
@@ -636,34 +640,75 @@
             connect(false);
         }
 
-        public void connectWithPartialValidConnectivity() {
-            setNetworkPartialValid();
-            connect(false);
+        public void connectWithPartialValidConnectivity(boolean isStrictMode) {
+            setNetworkPartialValid(isStrictMode);
+            connect(false, true /* hasInternet */, isStrictMode);
         }
 
-        void setNetworkValid() {
+        void setNetworkValid(boolean isStrictMode) {
             mNmValidationResult = VALIDATION_RESULT_VALID;
             mNmValidationRedirectUrl = null;
+            int probesSucceeded = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTP;
+            if (isStrictMode) {
+                probesSucceeded |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+            }
+            // The probesCompleted equals to probesSucceeded for the case of valid network, so put
+            // the same value into two different parameter of the method.
+            setProbesStatus(probesSucceeded, probesSucceeded);
         }
 
-        void setNetworkInvalid() {
+        void setNetworkInvalid(boolean isStrictMode) {
             mNmValidationResult = VALIDATION_RESULT_INVALID;
             mNmValidationRedirectUrl = null;
+            int probesCompleted = VALIDATION_RESULT_BASE;
+            int probesSucceeded = VALIDATION_RESULT_INVALID;
+            // If the isStrictMode is true, it means the network is invalid when NetworkMonitor
+            // tried to validate the private DNS but failed.
+            if (isStrictMode) {
+                probesCompleted &= ~NETWORK_VALIDATION_PROBE_HTTP;
+                probesSucceeded = probesCompleted;
+                probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+            }
+            setProbesStatus(probesCompleted, probesSucceeded);
         }
 
-        void setNetworkPortal(String redirectUrl) {
-            setNetworkInvalid();
+        void setNetworkPortal(String redirectUrl, boolean isStrictMode) {
+            setNetworkInvalid(isStrictMode);
             mNmValidationRedirectUrl = redirectUrl;
+            // Suppose the portal is found when NetworkMonitor probes NETWORK_VALIDATION_PROBE_HTTP
+            // in the beginning, so the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet.
+            int probesCompleted = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTPS;
+            int probesSucceeded = VALIDATION_RESULT_INVALID;
+            if (isStrictMode) {
+                probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+            }
+            setProbesStatus(probesCompleted, probesSucceeded);
         }
 
         void setNetworkPartial() {
             mNmValidationResult = VALIDATION_RESULT_PARTIAL;
             mNmValidationRedirectUrl = null;
+            int probesCompleted = VALIDATION_RESULT_BASE;
+            int probesSucceeded = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTPS;
+            setProbesStatus(probesCompleted, probesSucceeded);
         }
 
-        void setNetworkPartialValid() {
-            mNmValidationResult = VALIDATION_RESULT_PARTIAL | VALIDATION_RESULT_VALID;
-            mNmValidationRedirectUrl = null;
+        void setNetworkPartialValid(boolean isStrictMode) {
+            setNetworkPartial();
+            mNmValidationResult |= VALIDATION_RESULT_VALID;
+            int probesCompleted = VALIDATION_RESULT_BASE;
+            int probesSucceeded = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTPS;
+            // Suppose the partial network cannot pass the private DNS validation as well, so only
+            // add NETWORK_VALIDATION_PROBE_DNS in probesCompleted but not probesSucceeded.
+            if (isStrictMode) {
+                probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+            }
+            setProbesStatus(probesCompleted, probesSucceeded);
+        }
+
+        void setProbesStatus(int probesCompleted, int probesSucceeded) {
+            mProbesCompleted = probesCompleted;
+            mProbesSucceeded = probesSucceeded;
         }
 
         public String waitForRedirectUrl() {
@@ -2226,7 +2271,7 @@
 
         // With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
         // probe.
-        mWiFiNetworkAgent.setNetworkPartialValid();
+        mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
         // If the user chooses yes to use this partial connectivity wifi, switch the default
         // network to wifi and check if wifi becomes valid or not.
         mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
@@ -2299,7 +2344,7 @@
         callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
-        mWiFiNetworkAgent.setNetworkValid();
+        mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
 
         // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
         // validated.
@@ -2317,7 +2362,7 @@
         // NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as
         // valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls
         // notifyNetworkConnected.
-        mWiFiNetworkAgent.connectWithPartialValidConnectivity();
+        mWiFiNetworkAgent.connectWithPartialValidConnectivity(false /* isStrictMode */);
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
@@ -2343,7 +2388,7 @@
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String redirectUrl = "http://android.com/path";
-        mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl);
+        mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
 
@@ -2361,7 +2406,7 @@
                 mWiFiNetworkAgent);
 
         // Report partial connectivity is accepted.
-        mWiFiNetworkAgent.setNetworkPartialValid();
+        mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
         mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
                 false /* always */);
         waitForIdle();
@@ -2392,7 +2437,7 @@
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String firstRedirectUrl = "http://example.com/firstPath";
-        mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
+        mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
 
@@ -2405,13 +2450,13 @@
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String secondRedirectUrl = "http://example.com/secondPath";
-        mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
+        mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl, false /* isStrictMode */);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
 
         // Make captive portal disappear then revalidate.
         // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
-        mWiFiNetworkAgent.setNetworkValid();
+        mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
         captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
 
@@ -2423,7 +2468,7 @@
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
-        mWiFiNetworkAgent.setNetworkInvalid();
+        mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
         validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
     }
@@ -2454,7 +2499,7 @@
         mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
 
         // Turn into a captive portal.
-        mWiFiNetworkAgent.setNetworkPortal("http://example.com");
+        mWiFiNetworkAgent.setNetworkPortal("http://example.com", false /* isStrictMode */);
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -2475,7 +2520,7 @@
         assertEquals(testValue, signInIntent.getStringExtra(testKey));
 
         // Report that the captive portal is dismissed, and check that callbacks are fired
-        mWiFiNetworkAgent.setNetworkValid();
+        mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
         mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -2504,7 +2549,7 @@
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String firstRedirectUrl = "http://example.com/firstPath";
 
-        mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
+        mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
         mWiFiNetworkAgent.expectDisconnected();
         mWiFiNetworkAgent.expectPreventReconnectReceived();
 
@@ -3219,7 +3264,7 @@
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi.
-        mWiFiNetworkAgent.setNetworkInvalid();
+        mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -3263,7 +3308,7 @@
         wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi and expect the dialog to appear.
-        mWiFiNetworkAgent.setNetworkInvalid();
+        mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -4299,7 +4344,7 @@
         mCm.registerNetworkCallback(request, callback);
 
         // Bring up wifi aware network.
-        wifiAware.connect(false, false);
+        wifiAware.connect(false, false, false /* isStrictMode */);
         callback.expectAvailableCallbacksUnvalidated(wifiAware);
 
         assertNull(mCm.getActiveNetworkInfo());
@@ -4537,6 +4582,44 @@
     }
 
     @Test
+    public void testPrivateDnsNotification() throws Exception {
+        NetworkRequest request = new NetworkRequest.Builder()
+                .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(request, callback);
+        // Bring up wifi.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        // Private DNS resolution failed, checking if the notification will be shown or not.
+        mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        waitForIdle();
+        // If network validation failed, NetworkMonitor will re-evaluate the network.
+        // ConnectivityService should filter the redundant notification. This part is trying to
+        // simulate that situation and check if ConnectivityService could filter that case.
+        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        waitForIdle();
+        verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).notifyAsUser(anyString(),
+                eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any(), eq(UserHandle.ALL));
+        // If private DNS resolution successful, the PRIVATE_DNS_BROKEN notification shouldn't be
+        // shown.
+        mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
+        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        waitForIdle();
+        verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).cancelAsUser(anyString(),
+                eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), eq(UserHandle.ALL));
+        // If private DNS resolution failed again, the PRIVATE_DNS_BROKEN notification should be
+        // shown again.
+        mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        waitForIdle();
+        verify(mNotificationManager, timeout(TIMEOUT_MS).times(2)).notifyAsUser(anyString(),
+                eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any(), eq(UserHandle.ALL));
+    }
+
+    @Test
     public void testPrivateDnsSettingsChange() throws Exception {
         // Clear any interactions that occur as a result of CS starting up.
         reset(mMockDnsResolver);
@@ -4793,7 +4876,7 @@
         // by NetworkMonitor
         assertFalse(NetworkMonitorUtils.isValidationRequired(
                 vpnNetworkAgent.getNetworkCapabilities()));
-        vpnNetworkAgent.setNetworkValid();
+        vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
 
         vpnNetworkAgent.connect(false);
         mMockVpn.connect();
@@ -4882,7 +4965,8 @@
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
         mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+                false /* isStrictMode */);
         mMockVpn.connect();
 
         defaultCallback.assertNoCallback();
@@ -4913,7 +4997,8 @@
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
         mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */);
+        vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */,
+                false /* isStrictMode */);
         mMockVpn.connect();
 
         defaultCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
@@ -4945,7 +5030,8 @@
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
         mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */);
+        vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */,
+                false /* isStrictMode */);
         mMockVpn.connect();
 
         // Even though the VPN is unvalidated, it becomes the default network for our app.
@@ -4968,7 +5054,7 @@
                 vpnNetworkAgent.getNetworkCapabilities()));
 
         // Pretend that the VPN network validates.
-        vpnNetworkAgent.setNetworkValid();
+        vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
         vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         // Expect to see the validated capability, but no other changes, because the VPN is already
         // the default network for the app.
@@ -5000,7 +5086,8 @@
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
         mMockVpn.connect();
         mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+                false /* isStrictMode */);
 
         vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
         nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
@@ -5099,7 +5186,8 @@
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
         mMockVpn.connect();
         mMockVpn.setUids(ranges);
-        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+        vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+                false /* isStrictMode */);
 
         vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
         nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp
index f189048..9cef7b3 100644
--- a/tools/aapt2/format/Container.cpp
+++ b/tools/aapt2/format/Container.cpp
@@ -30,6 +30,7 @@
 
 constexpr const static uint32_t kContainerFormatMagic = 0x54504141u;
 constexpr const static uint32_t kContainerFormatVersion = 1u;
+constexpr const static size_t kPaddingAlignment = 4u;
 
 ContainerWriter::ContainerWriter(ZeroCopyOutputStream* out, size_t entry_count)
     : out_(out), total_entry_count_(entry_count), current_entry_count_(0u) {
@@ -49,11 +50,17 @@
   }
 }
 
-inline static void WritePadding(int padding, CodedOutputStream* out) {
-  if (padding < 4) {
-    const uint32_t zero = 0u;
-    out->WriteRaw(&zero, padding);
-  }
+inline static size_t CalculatePaddingForAlignment(size_t size) {
+  size_t overage = size % kPaddingAlignment;
+  return overage == 0 ? 0 : kPaddingAlignment - overage;
+}
+
+inline static void WritePadding(size_t padding, CodedOutputStream* out) {
+  CHECK(padding < kPaddingAlignment);
+  const uint32_t zero = 0u;
+  static_assert(sizeof(zero) >= kPaddingAlignment, "Not enough source bytes for padding");
+
+  out->WriteRaw(&zero, padding);
 }
 
 bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) {
@@ -70,7 +77,7 @@
 
   // Write the aligned size.
   const ::google::protobuf::uint64 size = table.ByteSize();
-  const int padding = 4 - (size % 4);
+  const int padding = CalculatePaddingForAlignment(size);
   coded_out.WriteLittleEndian64(size);
 
   // Write the table.
@@ -103,9 +110,9 @@
 
   // Write the aligned size.
   const ::google::protobuf::uint32 header_size = file.ByteSize();
-  const int header_padding = 4 - (header_size % 4);
+  const int header_padding = CalculatePaddingForAlignment(header_size);
   const ::google::protobuf::uint64 data_size = in->TotalSize();
-  const int data_padding = 4 - (data_size % 4);
+  const int data_padding = CalculatePaddingForAlignment(data_size);
   coded_out.WriteLittleEndian64(kResFileEntryHeaderSize + header_size + header_padding + data_size +
                                 data_padding);
 
diff --git a/tools/aapt2/formats.md b/tools/aapt2/formats.md
index bb31a00..25a0e79 100644
--- a/tools/aapt2/formats.md
+++ b/tools/aapt2/formats.md
@@ -23,7 +23,7 @@
 | Size (in bytes) | Field          | Description                                                                                               |
 |:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
 | `4`             | `entry_type`   | The type of the entry. This can be one of two types: `RES_TABLE (0x00000000)` or `RES_FILE (0x00000001)`. |
-| `8`             | `entry_length` | The length of the data that follows.                                                                      |
+| `8`             | `entry_length` | The length of the data that follows.  Do not use if `entry_type` is `RES_FILE`; this value may be wrong.  |
 | `entry_length`  | `data`         | The payload. The contents of this varies based on the `entry_type`.                                       |
 
 If the `entry_type` is equal to `RES_TABLE (0x00000000)`, the `data` field contains a serialized
@@ -32,13 +32,14 @@
 If the `entry_type` is equal to `RES_FILE (0x00000001)`, the `data` field contains the following:
 
 
-| Size (in bytes) | Field          | Description                                                                                               |
-|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
-| `4`             | `header_size`  | The size of the `header` field.                                                                 |
-| `8`             | `data_size`    | The size of the `data` field.                                                                   |
-| `header_size`   | `header`       | The serialized Protobuf message [aapt.pb.internal.CompiledFile](ResourcesInternal.proto).       |
-| `x`             | `padding`      | Up to 4 bytes of zeros, if padding is necessary to align the `data` field on a 32-bit boundary. |
-| `data_size`     | `data`         | The payload, which is determined by the `type` field in the `aapt.pb.internal.CompiledFile`. This can be a PNG file, binary XML, or [aapt.pb.XmlNode](Resources.proto). |
+| Size (in bytes) | Field            | Description                                                                                               |
+|:----------------|:-----------------|:----------------------------------------------------------------------------------------------------------|
+| `4`             | `header_size`    | The size of the `header` field.                                                                           |
+| `8`             | `data_size`      | The size of the `data` field.                                                                             |
+| `header_size`   | `header`         | The serialized Protobuf message [aapt.pb.internal.CompiledFile](ResourcesInternal.proto).                 |
+| `x`             | `header_padding` | Up to 3 bytes of zeros, if padding is necessary to align the `data` field on a 32-bit boundary.           |
+| `data_size`     | `data`           | The payload, which is determined by the `type` field in the `aapt.pb.internal.CompiledFile`. This can be a PNG file, binary XML, or [aapt.pb.XmlNode](Resources.proto). |
+| `y`             | `data_padding`   | Up to 3 bytes of zeros, if `data_size` is not a multiple of 4.                                            |
 
 ## AAPT2 Static Library Format (extension `.sapk`)
 
diff --git a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
index 3dfa4d2..bfbbf7a 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
@@ -27,19 +27,21 @@
 
         private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
         private const val PROTOLOGIMPL_CLASS_PARAM = "--protolog-impl-class"
+        private const val PROTOLOGCACHE_CLASS_PARAM = "--protolog-cache-class"
         private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
         private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
         private const val VIEWER_CONFIG_JSON_PARAM = "--viewer-conf"
         private const val OUTPUT_SOURCE_JAR_PARAM = "--output-srcjar"
         private val parameters = setOf(PROTOLOG_CLASS_PARAM, PROTOLOGIMPL_CLASS_PARAM,
-                PROTOLOGGROUP_CLASS_PARAM, PROTOLOGGROUP_JAR_PARAM, VIEWER_CONFIG_JSON_PARAM,
-                OUTPUT_SOURCE_JAR_PARAM)
+                PROTOLOGCACHE_CLASS_PARAM, PROTOLOGGROUP_CLASS_PARAM, PROTOLOGGROUP_JAR_PARAM,
+                VIEWER_CONFIG_JSON_PARAM, OUTPUT_SOURCE_JAR_PARAM)
 
         val USAGE = """
             Usage: ${Constants.NAME} <command> [<args>]
             Available commands:
 
             $TRANSFORM_CALLS_CMD $PROTOLOG_CLASS_PARAM <class name> $PROTOLOGIMPL_CLASS_PARAM
+                <class name> $PROTOLOGCACHE_CLASS_PARAM
                 <class name> $PROTOLOGGROUP_CLASS_PARAM <class name> $PROTOLOGGROUP_JAR_PARAM
                 <config.jar> $OUTPUT_SOURCE_JAR_PARAM <output.srcjar> [<input.java>]
             - processes java files replacing stub calls with logging code.
@@ -54,7 +56,7 @@
         """.trimIndent()
 
         private fun validateClassName(name: String): String {
-            if (!Pattern.matches("^([a-z]+[A-Za-z0-9]*\\.)+([A-Za-z0-9]+)$", name)) {
+            if (!Pattern.matches("^([a-z]+[A-Za-z0-9]*\\.)+([A-Za-z0-9$]+)$", name)) {
                 throw InvalidCommandException("Invalid class name $name")
             }
             return name
@@ -121,6 +123,7 @@
     val protoLogClassNameArg: String
     val protoLogGroupsClassNameArg: String
     val protoLogImplClassNameArg: String
+    val protoLogCacheClassNameArg: String
     val protoLogGroupsJarArg: String
     val viewerConfigJsonArg: String
     val outputSourceJarArg: String
@@ -170,6 +173,8 @@
                         params))
                 protoLogImplClassNameArg = validateClassName(getParam(PROTOLOGIMPL_CLASS_PARAM,
                         params))
+                protoLogCacheClassNameArg = validateClassName(getParam(PROTOLOGCACHE_CLASS_PARAM,
+                        params))
                 protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
                 viewerConfigJsonArg = validateNotSpecified(VIEWER_CONFIG_JSON_PARAM, params)
                 outputSourceJarArg = validateSrcJarName(getParam(OUTPUT_SOURCE_JAR_PARAM, params))
@@ -181,6 +186,7 @@
                 protoLogGroupsClassNameArg = validateClassName(getParam(PROTOLOGGROUP_CLASS_PARAM,
                         params))
                 protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+                protoLogCacheClassNameArg = validateNotSpecified(PROTOLOGCACHE_CLASS_PARAM, params)
                 protoLogGroupsJarArg = validateJarName(getParam(PROTOLOGGROUP_JAR_PARAM, params))
                 viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
                 outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
@@ -191,6 +197,7 @@
                 protoLogClassNameArg = validateNotSpecified(PROTOLOG_CLASS_PARAM, params)
                 protoLogGroupsClassNameArg = validateNotSpecified(PROTOLOGGROUP_CLASS_PARAM, params)
                 protoLogImplClassNameArg = validateNotSpecified(PROTOLOGIMPL_CLASS_PARAM, params)
+                protoLogCacheClassNameArg = validateNotSpecified(PROTOLOGCACHE_CLASS_PARAM, params)
                 protoLogGroupsJarArg = validateNotSpecified(PROTOLOGGROUP_JAR_PARAM, params)
                 viewerConfigJsonArg = validateJSONName(getParam(VIEWER_CONFIG_JSON_PARAM, params))
                 outputSourceJarArg = validateNotSpecified(OUTPUT_SOURCE_JAR_PARAM, params)
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index a8b9839..629f720 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -53,7 +53,8 @@
 
         command.javaSourceArgs.map { path ->
             executor.submitCallable {
-                val transformer = SourceTransformer(command.protoLogImplClassNameArg, processor)
+                val transformer = SourceTransformer(command.protoLogImplClassNameArg,
+                        command.protoLogCacheClassNameArg, processor)
                 val file = File(path)
                 val text = file.readText()
                 val outSrc = try {
@@ -80,10 +81,53 @@
 
         executor.shutdown()
 
+        val cacheSplit = command.protoLogCacheClassNameArg.split(".")
+        val cacheName = cacheSplit.last()
+        val cachePackage = cacheSplit.dropLast(1).joinToString(".")
+        val cachePath = "gen/${cacheSplit.joinToString("/")}.java"
+
+        outJar.putNextEntry(ZipEntry(cachePath))
+        outJar.write(generateLogGroupCache(cachePackage, cacheName, groups,
+                command.protoLogImplClassNameArg, command.protoLogGroupsClassNameArg).toByteArray())
+
         outJar.close()
         out.close()
     }
 
+    fun generateLogGroupCache(
+        cachePackage: String,
+        cacheName: String,
+        groups: Map<String, LogGroup>,
+        protoLogImplClassName: String,
+        protoLogGroupsClassName: String
+    ): String {
+        val fields = groups.values.map {
+            "public static boolean ${it.name}_enabled = false;"
+        }.joinToString("\n")
+
+        val updates = groups.values.map {
+            "${it.name}_enabled = " +
+                    "$protoLogImplClassName.isEnabled($protoLogGroupsClassName.${it.name});"
+        }.joinToString("\n")
+
+        return """
+            package $cachePackage;
+
+            public class $cacheName {
+${fields.replaceIndent("                ")}
+
+                static {
+                    $protoLogImplClassName.sCacheUpdater = $cacheName::update;
+                    update();
+                }
+
+                static void update() {
+${updates.replaceIndent("                    ")}
+                }
+            }
+        """.trimIndent()
+    }
+
     private fun tryParse(code: String, fileName: String): CompilationUnit {
         try {
             return StaticJavaParser.parse(code)
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index 9a38773..0ad8091 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -16,7 +16,6 @@
 
 package com.android.protolog.tool
 
-import com.android.protolog.tool.Constants.IS_ENABLED_METHOD
 import com.android.server.protolog.common.LogDataType
 import com.github.javaparser.StaticJavaParser
 import com.github.javaparser.ast.CompilationUnit
@@ -45,6 +44,7 @@
 
 class SourceTransformer(
     protoLogImplClassName: String,
+    protoLogCacheClassName: String,
     private val protoLogCallProcessor: ProtoLogCallProcessor
 ) : ProtoLogCallVisitor {
     override fun processCall(
@@ -91,10 +91,9 @@
             // Replace call to a stub method with an actual implementation.
             // Out: com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, null, arg)
             newCall.setScope(protoLogImplClassNode)
-            // Create a call to ProtoLogImpl.isEnabled(GROUP)
-            // Out: com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)
-            val isLogEnabled = MethodCallExpr(protoLogImplClassNode, IS_ENABLED_METHOD,
-                NodeList<Expression>(newCall.arguments[0].clone()))
+            // Create a call to ProtoLog$Cache.GROUP_enabled
+            // Out: com.android.server.protolog.ProtoLog$Cache.GROUP_enabled
+            val isLogEnabled = FieldAccessExpr(protoLogCacheClassNode, "${group.name}_enabled")
             if (argTypes.size != call.arguments.size - 2) {
                 throw InvalidProtoLogCallException(
                         "Number of arguments (${argTypes.size} does not mach format" +
@@ -211,6 +210,8 @@
 
     private val protoLogImplClassNode =
             StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
+    private val protoLogCacheClassNode =
+            StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogCacheClassName)
     private var processedCode: MutableList<String> = mutableListOf()
     private var offsets: IntArray = IntArray(0)
     private var fileName: String = ""
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
index 615712e..cf36651c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt
@@ -30,6 +30,7 @@
         )
         private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog"
         private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl"
+        private const val TEST_PROTOLOGCACHE_CLASS = "com.android.server.wm.ProtoLog\$Cache"
         private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup"
         private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" +
                 "services/core/services.core.wm.protologgroups/android_common/javac/" +
@@ -56,6 +57,7 @@
     fun transformClasses() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -73,6 +75,7 @@
     fun transformClasses_noProtoLogClass() {
         val testLine = "transform-protolog-calls " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -82,6 +85,17 @@
     @Test(expected = InvalidCommandException::class)
     fun transformClasses_noProtoLogImplClass() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_noProtoLogCacheClass() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -92,6 +106,7 @@
     fun transformClasses_noProtoLogGroupClass() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
         CommandOptions(testLine.split(' ').toTypedArray())
@@ -101,6 +116,7 @@
     fun transformClasses_noProtoLogGroupJar() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
         CommandOptions(testLine.split(' ').toTypedArray())
@@ -110,6 +126,7 @@
     fun transformClasses_noOutJar() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 TEST_JAVA_SRC.joinToString(" ")
@@ -120,6 +137,7 @@
     fun transformClasses_noJavaInput() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR"
@@ -130,6 +148,7 @@
     fun transformClasses_invalidProtoLogClass() {
         val testLine = "transform-protolog-calls --protolog-class invalid " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -140,6 +159,18 @@
     fun transformClasses_invalidProtoLogImplClass() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class invalid " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
+                "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
+                "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
+                "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
+        CommandOptions(testLine.split(' ').toTypedArray())
+    }
+
+    @Test(expected = InvalidCommandException::class)
+    fun transformClasses_invalidProtoLogCacheClass() {
+        val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
+                "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class invalid " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -150,6 +181,7 @@
     fun transformClasses_invalidProtoLogGroupClass() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class invalid " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -160,6 +192,7 @@
     fun transformClasses_invalidProtoLogGroupJar() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar invalid.txt " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -170,6 +203,7 @@
     fun transformClasses_invalidOutJar() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar invalid.db ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -180,6 +214,7 @@
     fun transformClasses_invalidJavaInput() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR invalid.py"
@@ -190,6 +225,7 @@
     fun transformClasses_unknownParam() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--unknown test --protolog-impl-class $TEST_PROTOLOGIMPL_CLASS " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
@@ -200,6 +236,7 @@
     fun transformClasses_noValue() {
         val testLine = "transform-protolog-calls --protolog-class $TEST_PROTOLOG_CLASS " +
                 "--protolog-impl-class " +
+                "--protolog-cache-class $TEST_PROTOLOGCACHE_CLASS " +
                 "--loggroups-class $TEST_PROTOLOGGROUP_CLASS " +
                 "--loggroups-jar $TEST_PROTOLOGGROUP_JAR " +
                 "--output-srcjar $TEST_SRC_JAR ${TEST_JAVA_SRC.joinToString(" ")}"
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt
new file mode 100644
index 0000000..ea9a58d
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogToolTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ProtoLogToolTest {
+
+    @Test
+    fun generateLogGroupCache() {
+        val groups = mapOf(
+                "GROUP1" to LogGroup("GROUP1", true, true, "TAG1"),
+                "GROUP2" to LogGroup("GROUP2", true, true, "TAG2")
+        )
+        val code = ProtoLogTool.generateLogGroupCache("org.example", "ProtoLog\$Cache",
+                groups, "org.example.ProtoLogImpl", "org.example.ProtoLogGroups")
+
+        assertEquals("""
+            package org.example;
+
+            public class ProtoLog${'$'}Cache {
+                public static boolean GROUP1_enabled = false;
+                public static boolean GROUP2_enabled = false;
+
+                static {
+                    org.example.ProtoLogImpl.sCacheUpdater = ProtoLog${'$'}Cache::update;
+                    update();
+                }
+
+                static void update() {
+                    GROUP1_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP1);
+                    GROUP2_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP2);
+                }
+            }
+        """.trimIndent(), code)
+    }
+}
\ No newline at end of file
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index e746300..6f5955c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -78,7 +78,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
                 }
             }
             """.trimIndent()
@@ -88,7 +88,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2); 
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2); 
             
             }
                 }
@@ -100,8 +100,8 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, "test %d %f", protoLogParam0, protoLogParam1); }
                 }
             }
             """.trimIndent()
@@ -111,7 +111,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { org.example.ProtoLogImpl.w(TEST_GROUP, -1741986185, 0, "test", (Object[]) null); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { org.example.ProtoLogImpl.w(TEST_GROUP, -1741986185, 0, "test", (Object[]) null); }
                 }
             }
             """.trimIndent()
@@ -121,7 +121,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, null, protoLogParam0, protoLogParam1); }
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1698911065, 9, null, protoLogParam0, protoLogParam1); }
                 }
             }
             """.trimIndent()
@@ -131,7 +131,7 @@
 
             class Test {
                 void test() {
-                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, null, protoLogParam0, protoLogParam1, protoLogParam2); 
+                    if (org.example.ProtoLogCache.TEST_GROUP_enabled) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 1780316587, 9, null, protoLogParam0, protoLogParam1, protoLogParam2); 
             
             }
                 }
@@ -165,8 +165,9 @@
     }
 
     private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
-    private val implPath = "org.example.ProtoLogImpl"
-    private val sourceJarWriter = SourceTransformer(implPath, processor)
+    private val implName = "org.example.ProtoLogImpl"
+    private val cacheName = "org.example.ProtoLogCache"
+    private val sourceJarWriter = SourceTransformer(implName, cacheName, processor)
 
     private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
 
@@ -191,8 +192,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(3, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
@@ -234,8 +234,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(3, ifStmts.size)
         val ifStmt = ifStmts[1]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(3, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
@@ -273,8 +272,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(4, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
@@ -311,8 +309,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(1, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
@@ -346,8 +343,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(3, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
@@ -385,8 +381,7 @@
         val ifStmts = code.findAll(IfStmt::class.java)
         assertEquals(1, ifStmts.size)
         val ifStmt = ifStmts[0]
-        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
-                ifStmt.condition.toString())
+        assertEquals("$cacheName.TEST_GROUP_enabled", ifStmt.condition.toString())
         assertFalse(ifStmt.elseStmt.isPresent)
         assertEquals(4, ifStmt.thenStmt.childNodes.size)
         val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 0b55794..62ba95d 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -27,6 +28,8 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -34,7 +37,7 @@
 import java.util.Locale;
 
 /**
- * Describes the state of any Wifi connection that is active or
+ * Describes the state of any Wi-Fi connection that is active or
  * is in the process of being set up.
  */
 public class WifiInfo implements Parcelable {
@@ -96,6 +99,47 @@
     private int mRssi;
 
     /**
+     * Wi-Fi unknown technology
+     */
+    public static final int WIFI_TECHNOLOGY_UNKNOWN = 0;
+
+    /**
+     * Wi-Fi 802.11a/b/g
+     */
+    public static final int WIFI_TECHNOLOGY_LEGACY = 1;
+
+    /**
+     * Wi-Fi 802.11n
+     */
+    public static final int WIFI_TECHNOLOGY_11N = 4;
+
+    /**
+     * Wi-Fi 802.11ac
+     */
+    public static final int WIFI_TECHNOLOGY_11AC = 5;
+
+    /**
+     * Wi-Fi 802.11ax
+     */
+    public static final int WIFI_TECHNOLOGY_11AX = 6;
+
+    /** @hide */
+    @IntDef(prefix = { "WIFI_TECHNOLOGY_" }, value = {
+            WIFI_TECHNOLOGY_UNKNOWN,
+            WIFI_TECHNOLOGY_LEGACY,
+            WIFI_TECHNOLOGY_11N,
+            WIFI_TECHNOLOGY_11AC,
+            WIFI_TECHNOLOGY_11AX
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WifiTechnology{}
+
+    /**
+     * Wi-Fi technology for the connection
+     */
+    private @WifiTechnology int mWifiTechnology;
+
+    /**
      * The unit in which links speeds are expressed.
      */
     public static final String LINK_SPEED_UNITS = "Mbps";
@@ -286,6 +330,7 @@
             txSuccessRate = source.txSuccessRate;
             rxSuccessRate = source.rxSuccessRate;
             score = source.score;
+            mWifiTechnology = source.mWifiTechnology;
         }
     }
 
@@ -374,6 +419,22 @@
     }
 
     /**
+     * Sets the Wi-Fi technology
+     * @hide
+     */
+    public void setWifiTechnology(@WifiTechnology int wifiTechnology) {
+        mWifiTechnology = wifiTechnology;
+    }
+
+    /**
+     * Get connection Wi-Fi technology
+     * @return the connection Wi-Fi technology
+     */
+    public @WifiTechnology int getWifiTechnology() {
+        return mWifiTechnology;
+    }
+
+    /**
      * Returns the current link speed in {@link #LINK_SPEED_UNITS}.
      * @return the link speed or {@link #LINK_SPEED_UNKNOWN} if link speed is unknown.
      * @see #LINK_SPEED_UNITS
@@ -679,6 +740,7 @@
                 .append(", MAC: ").append(mMacAddress == null ? none : mMacAddress)
                 .append(", Supplicant state: ")
                 .append(mSupplicantState == null ? none : mSupplicantState)
+                .append(", Wi-Fi technology: ").append(mWifiTechnology)
                 .append(", RSSI: ").append(mRssi)
                 .append(", Link speed: ").append(mLinkSpeed).append(LINK_SPEED_UNITS)
                 .append(", Tx Link speed: ").append(mTxLinkSpeed).append(LINK_SPEED_UNITS)
@@ -734,6 +796,7 @@
         dest.writeString(mNetworkSuggestionOrSpecifierPackageName);
         dest.writeString(mFqdn);
         dest.writeString(mProviderFriendlyName);
+        dest.writeInt(mWifiTechnology);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -775,6 +838,7 @@
                 info.mNetworkSuggestionOrSpecifierPackageName = in.readString();
                 info.mFqdn = in.readString();
                 info.mProviderFriendlyName = in.readString();
+                info.mWifiTechnology = in.readInt();
                 return info;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index dd9ee3b..04b073b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -38,6 +38,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.net.NetworkStack;
 import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -533,7 +534,9 @@
      *
      * @hide
      */
-    public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "wifi_ap_interface_name";
+    @SystemApi
+    public static final String EXTRA_WIFI_AP_INTERFACE_NAME =
+            "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
     /**
      * The intended ip mode for this softap.
      * @see #IFACE_IP_MODE_TETHERED
@@ -541,7 +544,8 @@
      *
      * @hide
      */
-    public static final String EXTRA_WIFI_AP_MODE = "wifi_ap_mode";
+    @SystemApi
+    public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
 
     /** @hide */
     @IntDef(flag = false, prefix = { "WIFI_AP_STATE_" }, value = {
@@ -647,6 +651,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int IFACE_IP_MODE_UNSPECIFIED = -1;
 
     /**
@@ -656,6 +661,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0;
 
     /**
@@ -665,6 +671,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int IFACE_IP_MODE_TETHERED = 1;
 
     /**
@@ -674,6 +681,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int IFACE_IP_MODE_LOCAL_ONLY = 2;
 
     /**
@@ -2661,16 +2669,21 @@
     /**
      * Call allowing ConnectivityService to update WifiService with interface mode changes.
      *
-     * The possible modes include: {@link #IFACE_IP_MODE_TETHERED},
-     *                             {@link #IFACE_IP_MODE_LOCAL_ONLY},
-     *                             {@link #IFACE_IP_MODE_CONFIGURATION_ERROR}
-     *
-     * @param ifaceName String name of the updated interface
-     * @param mode int representing the new mode
+     * @param ifaceName String name of the updated interface, or null to represent all interfaces
+     * @param mode int representing the new mode, one of:
+     *             {@link #IFACE_IP_MODE_TETHERED},
+     *             {@link #IFACE_IP_MODE_LOCAL_ONLY},
+     *             {@link #IFACE_IP_MODE_CONFIGURATION_ERROR},
+     *             {@link #IFACE_IP_MODE_UNSPECIFIED}
      *
      * @hide
      */
-    public void updateInterfaceIpState(String ifaceName, int mode) {
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+    })
+    public void updateInterfaceIpState(@Nullable String ifaceName, @IfaceIpMode int mode) {
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
@@ -2693,6 +2706,11 @@
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+    })
     public boolean startSoftAp(@Nullable WifiConfiguration wifiConfig) {
         try {
             IWifiManager iWifiManager = getIWifiManager();
@@ -2710,6 +2728,11 @@
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+    })
     public boolean stopSoftAp() {
         try {
             IWifiManager iWifiManager = getIWifiManager();
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index 6c2d7ff..ba9dd37 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -157,7 +157,8 @@
          */
         public @NonNull Builder setBssidPattern(
                 @NonNull MacAddress baseAddress, @NonNull MacAddress mask) {
-            checkNotNull(baseAddress, mask);
+            checkNotNull(baseAddress);
+            checkNotNull(mask);
             mBssidPatternMatcher = Pair.create(baseAddress, mask);
             return this;
         }
diff --git a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
index e8e8731..bb01365 100644
--- a/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
@@ -182,6 +182,12 @@
             throw new IOException("Passpoint profile missing credential");
         }
 
+        // Don't allow the installer to make changes to the update identifier. This is an
+        // indicator of an R2 (or newer) network.
+        if (config.getUpdateIdentifier() != Integer.MIN_VALUE) {
+            config.setUpdateIdentifier(Integer.MIN_VALUE);
+        }
+
         // Parse CA (Certificate Authority) certificate.
         byte[] caCertData = mimeParts.get(TYPE_CA_CERT);
         if (caCertData != null) {
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 557b7c9..e9aa076 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -65,6 +65,7 @@
      * Configurations under HomeSp subtree.
      */
     private HomeSp mHomeSp = null;
+
     /**
      * Set the Home SP (Service Provider) information.
      *
@@ -248,7 +249,10 @@
         mSubscriptionExpirationTimeInMillis = subscriptionExpirationTimeInMillis;
     }
     /**
-     * @hide
+     *  Utility method to get the time this subscription will expire. It is in the format of number
+     *  of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     *  @return The time this subscription will expire, or Long.MIN_VALUE to indicate unset value
      */
     public long getSubscriptionExpirationTimeInMillis() {
         return mSubscriptionExpirationTimeInMillis;
@@ -521,6 +525,8 @@
                 .append("\n");
         builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n");
         builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n");
+        builder.append("Provisioned by a subscription server: ")
+                .append(isOsuProvisioned() ? "Yes" : "No").append("\n");
         if (mHomeSp != null) {
             builder.append("HomeSP Begin ---\n");
             builder.append(mHomeSp);
@@ -728,4 +734,14 @@
         }
         return true;
     }
+
+    /**
+     * Indicates if the Passpoint Configuration was provisioned by a subscription (OSU) server,
+     * which means that it's an R2 (or R3) profile.
+     *
+     * @return true if the Passpoint Configuration was provisioned by a subscription server.
+     */
+    public boolean isOsuProvisioned() {
+        return getUpdateIdentifier() != Integer.MIN_VALUE;
+    }
 }
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64
new file mode 100644
index 0000000..f4d2abb
--- /dev/null
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64
@@ -0,0 +1,88 @@
+TUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7IGJvdW5kYXJ5
+PXtib3VuZGFyeX07IGNoYXJzZXQ9VVRGLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmFz
+ZTY0CgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi94LXBhc3Nwb2ludC1w
+cm9maWxlOyBjaGFyc2V0PVVURi04CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IGJhc2U2NAoK
+UEUxbmJYUlVjbVZsSUhodGJHNXpQU0p6ZVc1amJXdzZaRzFrWkdZeExqSWlQZ29nSUR4V1pYSkVW
+RVErTVM0eVBDOVdaWEpFVkVRKwpDaUFnUEU1dlpHVStDaUFnSUNBOFRtOWtaVTVoYldVK1VHVnlV
+SEp2ZG1sa1pYSlRkV0p6WTNKcGNIUnBiMjQ4TDA1dlpHVk9ZVzFsClBnb2dJQ0FnUEZKVVVISnZj
+R1Z5ZEdsbGN6NEtJQ0FnSUNBZ1BGUjVjR1UrQ2lBZ0lDQWdJQ0FnUEVSRVJrNWhiV1UrZFhKdU9u
+ZG0KWVRwdGJ6cG9iM1J6Y0c5ME1tUnZkREF0Y0dWeWNISnZkbWxrWlhKemRXSnpZM0pwY0hScGIy
+NDZNUzR3UEM5RVJFWk9ZVzFsUGdvZwpJQ0FnSUNBOEwxUjVjR1UrQ2lBZ0lDQThMMUpVVUhKdmNH
+VnlkR2xsY3o0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoCmJXVStWWEJrWVhSbFNX
+UmxiblJwWm1sbGNqd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lEeFdZV3gxWlQ0eE1qTTBQQzlXWVd4
+MVpUNEsKSUNBZ0lEd3ZUbTlrWlQ0S0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBOFRtOWtaVTVoYldV
+K2FUQXdNVHd2VG05a1pVNWhiV1UrQ2lBZwpJQ0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJS
+bFRtRnRaVDVJYjIxbFUxQThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJRHhPCmIyUmxQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGtaeWFXVnVaR3g1VG1GdFpUd3ZUbTlrWlU1aGJXVStDaUFn
+SUNBZ0lDQWcKSUNBOFZtRnNkV1UrUTJWdWRIVnllU0JJYjNWelpUd3ZWbUZzZFdVK0NpQWdJQ0Fn
+SUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4TwpiMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZP
+WVcxbFBrWlJSRTQ4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ1BGWmhiSFZsClBtMXBOaTVq
+Ynk1MWF6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4T2IyUmxQ
+Z29nSUNBZ0lDQWcKSUNBZ1BFNXZaR1ZPWVcxbFBsSnZZVzFwYm1kRGIyNXpiM0owYVhWdFQwazhM
+MDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnUEZaaApiSFZsUGpFeE1qSXpNeXcwTkRVMU5qWThM
+MVpoYkhWbFBnb2dJQ0FnSUNBZ0lEd3ZUbTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nCklDQWdJ
+Q0E4VG05a1pUNEtJQ0FnSUNBZ0lDQThUbTlrWlU1aGJXVStRM0psWkdWdWRHbGhiRHd2VG05a1pV
+NWhiV1UrQ2lBZ0lDQWcKSUNBZ1BFNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVTVoYldVK1Vt
+VmhiRzA4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZwpQRlpoYkhWbFBuTm9ZV3RsYmk1emRH
+bHljbVZrTG1OdmJUd3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnCklE
+eE9iMlJsUGdvZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBsVnpaWEp1WVcxbFVHRnpjM2R2Y21R
+OEwwNXZaR1ZPWVcxbFBnb2cKSUNBZ0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4
+T2IyUmxUbUZ0WlQ1VmMyVnlibUZ0WlR3dlRtOWtaVTVoYldVKwpDaUFnSUNBZ0lDQWdJQ0FnSUR4
+V1lXeDFaVDVxWVcxbGN6d3ZWbUZzZFdVK0NpQWdJQ0FnSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0Fn
+CklDQWdJQ0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk9ZVzFsUGxCaGMzTjNiM0pr
+UEM5T2IyUmxUbUZ0WlQ0S0lDQWcKSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQbGx0T1hWYVJFRjNUbmM5
+UFR3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNBOEwwNXZaR1UrQ2lBZwpJQ0FnSUNBZ0lDQThUbTlr
+WlQ0S0lDQWdJQ0FnSUNBZ0lDQWdQRTV2WkdWT1lXMWxQa1ZCVUUxbGRHaHZaRHd2VG05a1pVNWhi
+V1UrCkNpQWdJQ0FnSUNBZ0lDQWdJRHhPYjJSbFBnb2dJQ0FnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxU
+bUZ0WlQ1RlFWQlVlWEJsUEM5T2IyUmwKVG1GdFpUNEtJQ0FnSUNBZ0lDQWdJQ0FnSUNBOFZtRnNk
+V1UrTWpFOEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThMMDV2WkdVKwpDaUFnSUNBZ0lDQWdJ
+Q0FnSUR4T2IyUmxQZ29nSUNBZ0lDQWdJQ0FnSUNBZ0lEeE9iMlJsVG1GdFpUNUpibTVsY2sxbGRH
+aHZaRHd2ClRtOWtaVTVoYldVK0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnUEZaaGJIVmxQazFUTFVOSVFW
+QXRWakk4TDFaaGJIVmxQZ29nSUNBZ0lDQWcKSUNBZ0lDQThMMDV2WkdVK0NpQWdJQ0FnSUNBZ0lD
+QThMMDV2WkdVK0NpQWdJQ0FnSUNBZ1BDOU9iMlJsUGdvZ0lDQWdJQ0FnSUR4TwpiMlJsUGdvZ0lD
+QWdJQ0FnSUNBZ1BFNXZaR1ZPWVcxbFBrUnBaMmwwWVd4RFpYSjBhV1pwWTJGMFpUd3ZUbTlrWlU1
+aGJXVStDaUFnCklDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BFNXZaR1ZPWVcx
+bFBrTmxjblJwWm1sallYUmxWSGx3WlR3dlRtOWsKWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4
+V1lXeDFaVDU0TlRBNWRqTThMMVpoYkhWbFBnb2dJQ0FnSUNBZ0lDQWdQQzlPYjJSbApQZ29nSUNB
+Z0lDQWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmxUbUZ0WlQ1RFpYSjBVMGhC
+TWpVMlJtbHVaMlZ5CmNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZz
+ZFdVK01XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXgKWmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4
+WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaand2Vm1Gc2RXVStDaUFnSUNBZwpJQ0Fn
+SUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWdJRHhPYjJSbFBnb2dJ
+Q0FnSUNBZ0lDQWdQRTV2ClpHVk9ZVzFsUGxOSlRUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJ
+Q0E4VG05a1pUNEtJQ0FnSUNBZ0lDQWdJQ0FnUEU1dlpHVk8KWVcxbFBrbE5VMGs4TDA1dlpHVk9Z
+VzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdVK2FXMXphVHd2Vm1Gc2RXVStDaUFnSUNBZwpJ
+Q0FnSUNBOEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnSUNBOFRtOWtaVDRLSUNBZ0lDQWdJQ0FnSUNBZ1BF
+NXZaR1ZPWVcxbFBrVkJVRlI1CmNHVThMMDV2WkdWT1lXMWxQZ29nSUNBZ0lDQWdJQ0FnSUNBOFZt
+RnNkV1UrTWpROEwxWmhiSFZsUGdvZ0lDQWdJQ0FnSUNBZ1BDOU8KYjJSbFBnb2dJQ0FnSUNBZ0lE
+d3ZUbTlrWlQ0S0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ1BDOU9iMlJsUGdvZ0lEd3ZUbTlrWlQ0
+SwpQQzlOWjIxMFZISmxaVDRLCgotLXtib3VuZGFyeX0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlv
+bi94LXg1MDktY2EtY2VydApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBiYXNlNjQKCkxTMHRM
+UzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVVJMUkVORFFXaERaMEYzU1VKQlow
+bEtRVWxNYkVaa2QzcE0KVm5WeVRVRXdSME5UY1VkVFNXSXpSRkZGUWtOM1ZVRk5Ra2w0UlVSQlQw
+Sm5UbFlLUWtGTlZFSXdWa0pWUTBKRVVWUkZkMGhvWTA1TgpWRmwzVFZSRmVVMVVSVEZOUkVVeFYy
+aGpUazFxV1hkTlZFRTFUVlJGTVUxRVJURlhha0ZUVFZKQmR3cEVaMWxFVmxGUlJFVjNaRVpSClZr
+Rm5VVEJGZUUxSlNVSkpha0ZPUW1kcmNXaHJhVWM1ZHpCQ1FWRkZSa0ZCVDBOQlVUaEJUVWxKUWtO
+blMwTkJVVVZCQ25wdVFWQlYKZWpJMlRYTmhaVFIzY3pRelkzcFNOREV2U2pKUmRISlRTVnBWUzIx
+V1ZYTldkVzFFWWxsSWNsQk9kbFJZUzFOTldFRmpaWGRQVWtSUgpXVmdLVW5GMlNIWndiamhEYzJO
+Q01TdHZSMWhhZGtoM2VHbzBlbFl3VjB0dlN6SjZaVmhyWVhVemRtTjViRE5JU1V0MWNFcG1jVEpV
+ClJVRkRaV1pXYW1vd2RBcEtWeXRZTXpWUVIxZHdPUzlJTlhwSlZVNVdUbFpxVXpkVmJYTTRORWwy
+UzJoU1FqZzFNVEpRUWpsVmVVaGgKWjFoWlZsZzFSMWR3UVdOV2NIbG1jbXhTQ2taSk9WRmthR2dy
+VUdKck1IVjVhM1JrWW1ZdlEyUm1aMGhQYjJWaWNsUjBkMUpzYWswdwpiMFIwV0NzeVEzWTJhakIz
+UWtzM2FFUTRjRkIyWmpFcmRYa0tSM3BqZW1sblFWVXZORXQzTjJWYWNYbGtaamxDS3pWU2RYQlNL
+MGxhCmFYQllOREY0UldsSmNrdFNkM0ZwTlRFM1YxZDZXR05xWVVjeVkwNWlaalExTVFwNGNFZzFV
+RzVXTTJreGRIRXdOR3BOUjFGVmVrWjMKU1VSQlVVRkNielJIUVUxSU5IZElVVmxFVmxJd1QwSkNX
+VVZHU1hkWU5IWnpPRUpwUW1OVFkyOWtDalZ1YjFwSVVrMDRSVFFyYVUxRgpTVWRCTVZWa1NYZFJO
+MDFFYlVGR1NYZFlOSFp6T0VKcFFtTlRZMjlrTlc1dldraFNUVGhGTkN0cGIxSmhhMFpFUVZNS1RW
+SkJkMFJuCldVUldVVkZFUlhka1JsRldRV2RSTUVWNFoyZHJRV2QxVlZZelJFMTBWelp6ZDBSQldV
+UldVakJVUWtGVmQwRjNSVUl2ZWtGTVFtZE8KVmdwSVVUaEZRa0ZOUTBGUldYZEVVVmxLUzI5YVNX
+aDJZMDVCVVVWTVFsRkJSR2RuUlVKQlJtWlJjVTlVUVRkU2RqZExLMngxVVRkdwpibUZ6TkVKWmQw
+aEZDamxIUlZBdmRXOW9kalpMVDNrd1ZFZFJSbUp5VWxScVJtOU1WazVDT1VKYU1YbHRUVVJhTUM5
+VVNYZEpWV00zCmQyazNZVGgwTlcxRmNWbElNVFV6ZDFjS1lWZHZiMmxUYW5sTVRHaDFTVFJ6VG5K
+T1EwOTBhWE5rUW5FeWNqSk5SbGgwTm1nd2JVRlIKV1U5UWRqaFNPRXMzTDJablUzaEhSbkY2YUhs
+T2JXMVdUQW94Y1VKS2JHUjRNelJUY0hkelZFRk1VVlpRWWpSb1IzZEtlbHBtY2pGUQpZM0JGVVhn
+MmVFMXVWR3c0ZUVWWFdrVXpUWE01T1hWaFZYaGlVWEZKZDFKMUNreG5RVTlyVGtOdFdUSnRPRGxX
+YUhwaFNFb3hkVlk0Ck5VRmtUUzkwUkN0WmMyMXNibTVxZERsTVVrTmxhbUpDYVhCcVNVZHFUMWh5
+WnpGS1VDdHNlRllLYlhWTk5IWklLMUF2Yld4dGVITlEKVUhvd1pEWTFZaXRGUjIxS1duQnZUR3RQ
+TDNSa1RrNTJRMWw2YWtwd1ZFVlhjRVZ6VHpaT1RXaExXVzg5Q2kwdExTMHRSVTVFSUVORgpVbFJK
+UmtsRFFWUkZMUzB0TFMwSwotLXtib3VuZGFyeX0tLQo=
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index 0ce5d66..ea08ea8 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -38,6 +38,7 @@
     private static final String TEST_PACKAGE_NAME = "com.test.example";
     private static final String TEST_FQDN = "test.com";
     private static final String TEST_PROVIDER_NAME = "test";
+    private static final int TEST_WIFI_TECHNOLOGY = WifiInfo.WIFI_TECHNOLOGY_11AC;
 
     /**
      *  Verify parcel write/read with WifiInfo.
@@ -54,6 +55,7 @@
         writeWifiInfo.setFQDN(TEST_FQDN);
         writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
         writeWifiInfo.setNetworkSuggestionOrSpecifierPackageName(TEST_PACKAGE_NAME);
+        writeWifiInfo.setWifiTechnology(TEST_WIFI_TECHNOLOGY);
 
         Parcel parcel = Parcel.obtain();
         writeWifiInfo.writeToParcel(parcel, 0);
@@ -72,5 +74,6 @@
         assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getNetworkSuggestionOrSpecifierPackageName());
         assertEquals(TEST_FQDN, readWifiInfo.getPasspointFqdn());
         assertEquals(TEST_PROVIDER_NAME, readWifiInfo.getPasspointProviderFriendlyName());
+        assertEquals(TEST_WIFI_TECHNOLOGY, readWifiInfo.getWifiTechnology());
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
index d9a1d9af..c815d75 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
@@ -54,6 +54,8 @@
             "assets/hsr1/HSR1ProfileWithInvalidContentType.base64";
     private static final String PASSPOINT_INSTALLATION_FILE_WITHOUT_PROFILE =
             "assets/hsr1/HSR1ProfileWithoutProfile.base64";
+    private static final String PASSPOINT_INSTALLATION_FILE_WITH_UPDATE_ID =
+            "assets/hsr1/HSR1ProfileWithUpdateIdentifier.base64";
 
     /**
      * Read the content of the given resource file into a String.
@@ -201,4 +203,22 @@
         assertNull(ConfigParser.parsePasspointConfig(
                 "application/x-wifi-config", configStr.getBytes()));
     }
+
+    /**
+     * Verify a valid installation file is parsed successfully with the matching contents, and that
+     * Update identifier is cleared.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseConfigFileWithUpdateIdentifier() throws Exception {
+        String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_UPDATE_ID);
+        PasspointConfiguration expectedConfig = generateConfigurationFromProfile();
+        PasspointConfiguration actualConfig =
+                ConfigParser.parsePasspointConfig(
+                        "application/x-wifi-config", configStr.getBytes());
+        // Expected configuration does not contain an update identifier
+        assertTrue(actualConfig.equals(expectedConfig));
+    }
+
 }
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index c3b074e..f501b16 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -183,7 +183,7 @@
         PasspointConfiguration config = PasspointTestUtils.createConfig();
 
         assertTrue(config.validate());
-        assertTrue(config.validateForR2());
+        assertFalse(config.isOsuProvisioned());
     }
 
     /**
@@ -241,7 +241,6 @@
         config.setPolicy(null);
 
         assertTrue(config.validate());
-        assertTrue(config.validateForR2());
     }
 
     /**
@@ -271,7 +270,6 @@
         config.setAaaServerTrustedNames(null);
 
         assertTrue(config.validate());
-        assertTrue(config.validateForR2());
     }
 
     /**
@@ -348,4 +346,17 @@
         PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
         assertTrue(copyConfig.equals(sourceConfig));
     }
+
+    /**
+     * Verify that a configuration containing all fields is valid for R2.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateFullR2Config() throws Exception {
+        PasspointConfiguration config = PasspointTestUtils.createR2Config();
+        assertTrue(config.validate());
+        assertTrue(config.validateForR2());
+        assertTrue(config.isOsuProvisioned());
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
index adf74eb..8d55acb 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
@@ -132,7 +132,6 @@
      */
     public static PasspointConfiguration createConfig() {
         PasspointConfiguration config = new PasspointConfiguration();
-        config.setUpdateIdentifier(1234);
         config.setHomeSp(createHomeSp());
         config.setAaaServerTrustedNames(
                 new String[] {"trusted.fqdn.com", "another-trusted.fqdn.com"});
@@ -145,7 +144,6 @@
         trustRootCertList.put("trustRoot.cert2.com",
                 new byte[CERTIFICATE_FINGERPRINT_BYTES]);
         config.setTrustRootCertList(trustRootCertList);
-        config.setUpdateIdentifier(1);
         config.setCredentialPriority(120);
         config.setSubscriptionCreationTimeInMillis(231200);
         config.setSubscriptionExpirationTimeInMillis(2134232);
@@ -160,4 +158,15 @@
         config.setServiceFriendlyNames(friendlyNames);
         return config;
     }
+
+    /**
+     * Helper function for creating an R2 {@link PasspointConfiguration} for testing.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    public static PasspointConfiguration createR2Config() {
+        PasspointConfiguration config = createConfig();
+        config.setUpdateIdentifier(1234);
+        return config;
+    }
 }